Closed sirzooro closed 2 years ago
Hmm, the docs are lacking in this area, thanks for bringing this up!
When I built on top of jwt-cpp (v0.5.0), I had two application requirements
The first was easy, I was already using RapidJSON to do that. The second, before the verifier API, I just accessed the nested private claim (which I knew was there from the JSON schema) and looked up my two required roles.
We now have https://github.com/Thalhammer/jwt-cpp/blob/9d1a010fea3ed3293112edec9745dc435a85a849/include/jwt-cpp/jwt.h#L3012 so that might help
However only the "top level" claims are passed to the verifier, https://github.com/Thalhammer/jwt-cpp/blob/master/include/jwt-cpp/jwt.h#L3299
That's likely the best way to
verify this claim piece by piece
Hopefully that helps answer your questions 🤞
Thanks! With these hints I was able to successfully create my custom validator.
During my work I found few issues. They do not block me, but it would be nice if you could address them in the future:
jwt::claim
type. It compiled after I removed this explicit type cast;jwt::traits::nlohmann_json
as a template parameter in few places. Please provide some #define
to specify default traits type, it would simplify code a bit;jwt::basic_claim
has no as_object()
method. Fortunately to_json()
could be used instead, at least for nlohmann::json
;std::system_error
. It would be handy if you could use some library-specific base class instead, e.g. jwt::validation_error
. This would allow to write bigger block of code, and at the end add few catch
sections for different kinds of errors (e.g. network error, jwt validation error, etc.);I have noticed one more issue: error message for exception (returned by what()
) thrown after jwt::error::token_verification_error::claim_value_missmatch
error is reported does not include claim name which did not pass validation. This name would be useful during debugging of connection issues caused by incorrect token claims. This issue probably also applies to other generic claim verification errors.
error is reported does not include claim name which did not pass validation
I agree that this would be helpfull, but thats hard to do. The way it works is that jwt-cpp builds a std::error_code
and throws a std::system_error
containing the error code. While this is usually a good design because it provides a very consistent and stl friendly interface, it makes adding runtime information to the message/exception near impossible. std::error_code
only stores a int error code and a pointer to an error category. However it does not own the category (it needs to be valid for the lifetime of the error_code, that usually means a static singleton). If the message is requested it calls a method on the category passing the error code, which than returns a (also static) c style string. Including runtime info usually means some kind of hack or a memory leak.
error is reported does not include claim name which did not pass validation
I agree that this would be helpfull, but thats hard to do. The way it works is that jwt-cpp builds a
std::error_code
and throws astd::system_error
containing the error code. While this is usually a good design because it provides a very consistent and stl friendly interface, it makes adding runtime information to the message/exception near impossible.std::error_code
only stores a int error code and a pointer to an error category. However it does not own the category (it needs to be valid for the lifetime of the error_code, that usually means a static singleton). If the message is requested it calls a method on the category passing the error code, which than returns a (also static) c style string. Including runtime info usually means some kind of hack or a memory leak.
Custom exception class derived from std::system_error
could have extra field to store claim name, and some method like claim_name()
to get it. This would be enough for me to build meaningful log message, so you could leave default implementation of what()
.
- code from rsa-create.cpp example does not compile - compiler complained about unknown
jwt::claim
type. It compiled after I removed this explicit type cast;
Hmm 🤔 that's very odd.... https://github.com/Thalhammer/jwt-cpp/runs/5156063246?check_suite_focus=true#step:7:10 it's Passing in CI.
- I had to explicitly specify
jwt::traits::nlohmann_json
as a template parameter in few places. Please provide some#define
to specify default traits type, it would simplify code a bit;
That explains above, it made for the default choice of picojson!
Did you try including https://github.com/Thalhammer/jwt-cpp/blob/master/include/jwt-cpp/traits/nlohmann-json/defaults.h? There's no docs so sorry for the confusion. Let me know if that helps
all jwt-cpp exception types are derived directly from std::system_error. It would be handy if you could use some library-specific base class instead, e.g. jwt::validation_error. This would allow to write bigger block of code, and at the end
This is a really interesting point! Feel free to open a new issue so we can track that idea 🙏
and of course provide some example how to create and use custom validator
If you can share you code, I would love to turn it into an example!
Thank you so much for the feedback, I am glad there was just enough flexibility to keep you moving forward 🚀
- I had to explicitly specify
jwt::traits::nlohmann_json
as a template parameter in few places. Please provide some#define
to specify default traits type, it would simplify code a bit;That explains above, it made for the default choice of picojson!
Did you try including https://github.com/Thalhammer/jwt-cpp/blob/master/include/jwt-cpp/traits/nlohmann-json/defaults.h? There's no docs so sorry for the confusion. Let me know if that helps
Thanks, this helped and provided new default traits in most places. I still had to specify it for jwt::verify_ops::verify_context<>
.
I copied #include
from jwt-cpp/example/traits/nlohmann-json.cpp, please update that example too.
all jwt-cpp exception types are derived directly from std::system_error. It would be handy if you could use some library-specific base class instead, e.g. jwt::validation_error. This would allow to write bigger block of code, and at the end
This is a really interesting point! Feel free to open a new issue so we can track that idea 🙏
Will do.
and of course provide some example how to create and use custom validator
If you can share you code, I would love to turn it into an example!
Here you are. It is mostly copy/paste of your examples. My code uses nlohmann::json
directly to handle JSON stuff, so you would have to update it for PicoJson:
#include <jwt-cpp/traits/nlohmann-json/defaults.h>
#include <iostream>
int main()
{
std::string rsa_priv_key = R"(-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC4ZtdaIrd1BPIJ
tfnF0TjIK5inQAXZ3XlCrUlJdP+XHwIRxdv1FsN12XyMYO/6ymLmo9ryoQeIrsXB
XYqlET3zfAY+diwCb0HEsVvhisthwMU4gZQu6TYW2s9LnXZB5rVtcBK69hcSlA2k
ZudMZWxZcj0L7KMfO2rIvaHw/qaVOE9j0T257Z8Kp2CLF9MUgX0ObhIsdumFRLaL
DvDUmBPr2zuh/34j2XmWwn1yjN/WvGtdfhXW79Ki1S40HcWnygHgLV8sESFKUxxQ
mKvPUTwDOIwLFL5WtE8Mz7N++kgmDcmWMCHc8kcOIu73Ta/3D4imW7VbKgHZo9+K
3ESFE3RjAgMBAAECggEBAJTEIyjMqUT24G2FKiS1TiHvShBkTlQdoR5xvpZMlYbN
tVWxUmrAGqCQ/TIjYnfpnzCDMLhdwT48Ab6mQJw69MfiXwc1PvwX1e9hRscGul36
ryGPKIVQEBsQG/zc4/L2tZe8ut+qeaK7XuYrPp8bk/X1e9qK5m7j+JpKosNSLgJj
NIbYsBkG2Mlq671irKYj2hVZeaBQmWmZxK4fw0Istz2WfN5nUKUeJhTwpR+JLUg4
ELYYoB7EO0Cej9UBG30hbgu4RyXA+VbptJ+H042K5QJROUbtnLWuuWosZ5ATldwO
u03dIXL0SH0ao5NcWBzxU4F2sBXZRGP2x/jiSLHcqoECgYEA4qD7mXQpu1b8XO8U
6abpKloJCatSAHzjgdR2eRDRx5PMvloipfwqA77pnbjTUFajqWQgOXsDTCjcdQui
wf5XAaWu+TeAVTytLQbSiTsBhrnoqVrr3RoyDQmdnwHT8aCMouOgcC5thP9vQ8Us
rVdjvRRbnJpg3BeSNimH+u9AHgsCgYEA0EzcbOltCWPHRAY7B3Ge/AKBjBQr86Kv
TdpTlxePBDVIlH+BM6oct2gaSZZoHbqPjbq5v7yf0fKVcXE4bSVgqfDJ/sZQu9Lp
PTeV7wkk0OsAMKk7QukEpPno5q6tOTNnFecpUhVLLlqbfqkB2baYYwLJR3IRzboJ
FQbLY93E8gkCgYB+zlC5VlQbbNqcLXJoImqItgQkkuW5PCgYdwcrSov2ve5r/Acz
FNt1aRdSlx4176R3nXyibQA1Vw+ztiUFowiP9WLoM3PtPZwwe4bGHmwGNHPIfwVG
m+exf9XgKKespYbLhc45tuC08DATnXoYK7O1EnUINSFJRS8cezSI5eHcbQKBgQDC
PgqHXZ2aVftqCc1eAaxaIRQhRmY+CgUjumaczRFGwVFveP9I6Gdi+Kca3DE3F9Pq
PKgejo0SwP5vDT+rOGHN14bmGJUMsX9i4MTmZUZ5s8s3lXh3ysfT+GAhTd6nKrIE
kM3Nh6HWFhROptfc6BNusRh1kX/cspDplK5x8EpJ0QKBgQDWFg6S2je0KtbV5PYe
RultUEe2C0jYMDQx+JYxbPmtcopvZQrFEur3WKVuLy5UAy7EBvwMnZwIG7OOohJb
vkSpADK6VPn9lbqq7O8cTedEHttm6otmLt8ZyEl3hZMaL3hbuRj6ysjmoFKx6CrX
rK0/Ikt5ybqUzKCMJZg2VKGTxg==
-----END PRIVATE KEY-----)";
auto testRoleClaim = nlohmann::json{
{"my-service", {
{"roles", { "foo", "bar", "baz" }}
}}
};
auto token = jwt::create()
.set_issuer("auth0")
.set_type("JWT")
.set_id("rsa-create-example")
.set_issued_at(std::chrono::system_clock::now())
.set_expires_at(std::chrono::system_clock::now() + std::chrono::seconds{36000})
.set_payload_claim("resource_access", testRoleClaim)
.sign(jwt::algorithm::rs256("", rsa_priv_key, "", ""));
std::cout << "token:\n" << token << std::endl;
std::string rsa_pub_key = R"(-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuGbXWiK3dQTyCbX5xdE4
yCuYp0AF2d15Qq1JSXT/lx8CEcXb9RbDddl8jGDv+spi5qPa8qEHiK7FwV2KpRE9
83wGPnYsAm9BxLFb4YrLYcDFOIGULuk2FtrPS512Qea1bXASuvYXEpQNpGbnTGVs
WXI9C+yjHztqyL2h8P6mlThPY9E9ue2fCqdgixfTFIF9Dm4SLHbphUS2iw7w1JgT
69s7of9+I9l5lsJ9cozf1rxrXX4V1u/SotUuNB3Fp8oB4C1fLBEhSlMcUJirz1E8
AziMCxS+VrRPDM+zfvpIJg3JljAh3PJHDiLu902v9w+Iplu1WyoB2aPfitxEhRN0
YwIDAQAB
-----END PUBLIC KEY-----)";
auto decoded = jwt::decode(token);
for (const auto& e : decoded.get_payload_claims())
std::cout << e.first << " = " << e.second << std::endl;
std::cout << std::endl;
auto roleVerifier = [](const jwt::verify_ops::verify_context<jwt::traits::nlohmann_json>& ctx, std::error_code& ec)
{
auto c = ctx.get_claim(false, ec);
if (ec)
return;
if (c.get_type() == jwt::json::type::object)
{
auto obj = c.to_json();
try
{
auto roles = obj["my-service"]["roles"].get<nlohmann::json::array_t>();
if (roles.end() == std::find(roles.begin(), roles.end(), "foo"))
ec = jwt::error::token_verification_error::claim_value_missmatch;
}
catch (const std::exception& ex)
{
ec = jwt::error::token_verification_error::claim_value_missmatch;
}
}
else
ec = jwt::error::token_verification_error::claim_type_missmatch;
};
auto verifier = jwt::verify()
.allow_algorithm(jwt::algorithm::rs256(rsa_pub_key, "", "", ""))
.with_issuer("auth0")
.with_claim("resource_access", roleVerifier);
try
{
verifier.verify(decoded);
std::cout << "Success!" << std::endl;
}
catch (const std::exception& ex)
{
std::cout << "Error: " << ex.what() << std::endl;
}
return 0;
}
Thank you so much! Hopefully in the coming weeks we can fold in your suggestions 🤗
@prince-chrismc
Sorry to comment on this old issue, I'm just wondering about one thing that you mentioned here:
The first was easy, I was already using RapidJSON to do that.
I was just wondering if this library supports using rapidjson as the json parser using the json traits, I was trying to find some info and found this other issue which seems to suggest it isn't supported: https://github.com/Thalhammer/jwt-cpp/issues/96#issuecomment-673352191
But then also found your comment above which seems to suggest that you were using rapidjson? Were you using rapidjson as the parser for jwt-cpp using the traits, or do you just mean you were using it separately just taking the json and parsing it again using rapidjson or something like that?
Ughs 4 jobs ago... 🤔 The later. We used picojson with jwt-cpp and Marshalled the data back and forth to use it with rapidjson for schema validation.
Rapidjson could be used with jwt-cpp, the effort required probably isn't worth it. Its likely just as much work to switch for another json library.
Thanks for the reply, just wanted to check, yeah, we'll just use another library for use with jwt-cpp then.
Describe the impediment RedHat SSO allows configuration of roles for given service. When service gets its JWT token from SSO, it contains following claim with these roles:
Trying to obtain I need to verify that service called "my-service" has role "bar", and ignore any other roles present in this claim. I found example where custom claim value is checked against JSON object, but this is not exactly my case - I need to check for this JSON structure ("resource_access", "my-service", "roles" elements) and verify that roles array contains proper element. Is there a way to do this using
.with_claim("resource_access", ...)
? Or do I have to useverifier.verify()
first, and then manually extract and verify this claim piece by piece?Desktop: