Thalhammer / jwt-cpp

A header only library for creating and validating json web tokens in c++
https://thalhammer.github.io/jwt-cpp/
MIT License
828 stars 229 forks source link

Need help : How to Implement JWT Operations from Python Using jwt-cpp ? #337

Closed ngbruce closed 3 months ago

ngbruce commented 3 months ago

What's your question?

How to Implement JWT Operations from Python Using jwt-cpp

Additional Context

Hello, I need some help on this. I have the following Python code:

def generate_token(apikey: str, exp_seconds: int):
    try:
        api_key, secret = apikey.split(".")
        print(api_key, secret)
    except Exception as e:
        raise Exception("invalid apikey", e)

    time_rec = time.time()
    exp_time = int(round(time_rec * 1000)) + exp_seconds * 1000
    timestamp = int(round(time_rec * 1000))
    payload = {
        "api_key": api_key,
        "exp": exp_time,
        "timestamp": timestamp,
    }

    return jwt.encode(
        payload,
        secret,
        algorithm="HS256",
        headers={"alg": "HS256", "sign_type": "SIGN"},
    )

I want to achieve the same functionality in Qt C++ using jwt-cpp. I have written the following code:

auto token = jwt::create()
                 .set_algorithm("HS256")
                 .set_issuer("auth0")
                 .set_type("JWS")
                 .set_issued_now()
                 .set_expires_in(std::chrono::seconds{3600})
                 .set_payload_claim("api_key", jwt::claim(std::string("example_of_the_api_key")))
                 .sign(jwt::algorithm::hs256{"example_of_the_secret"});

qDebug()<<token;

However, while this C++ code runs, the generated token has a different length compared to the one generated by the Python code, and it cannot be verified by the server. I'm not quite sure how to translate some of the operations from the Python version into jwt-cpp. For example, where should I place "sign_type": "SIGN" in jwt-cpp? Could someone please provide me with some guidance? Thank you.

Thalhammer commented 3 months ago

Hi

For example, where should I place "sign_type": "SIGN" in jwt-cpp?

You can use set_header_claim similar to the one you used for the payload to set a claim inside the jwt header. That being said, the header is usually not used for custom claims.

Your C++ version also misses the timestamp claim (but sets the standard iat claim instead).

The length isn't necessarily a good indicator, as it can vary depending on the formatting of the json and claims that are set automatically. If you want to verify two tokens are similar you can drop it into the jwt debugger and check the claims contained inside.

ngbruce commented 3 months ago

Hi, thank you for reply. About the timestamp caim you mentioned, In the c++ verson, I placed

.set_issued_now()
.set_expires_in(std::chrono::seconds{3600})

so I still need to place them with .set_payload_claim()? If so could you give me an example for I am new to it. And also about the set_header_claim, cou you provide example fot it? Thanks you.

Thalhammer commented 3 months ago

In the c++ verson, I placed

.set_issued_now()
.set_expires_in(std::chrono::seconds{3600})

This sets the standardized iat (issued at) and exp (expires) claims. Your python example however also sets a custom timestamp claim (which seems to serve the purpose of iat, so I'd recommend porting to that in your existing codebase if thats an option).

To do that you'd have to do something like this:

.set_payload_claim("timestamp", std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count())

You can set your "sign_type" claim using something like this:

.set_header_claim("sign_type", "SIGN")
ngbruce commented 3 months ago

In the c++ verson, I placed

.set_issued_now()
.set_expires_in(std::chrono::seconds{3600})

This sets the standardized iat (issued at) and exp (expires) claims. Your python example however also sets a custom timestamp claim (which seems to serve the purpose of iat, so I'd recommend porting to that in your existing codebase if thats an option).

To do that you'd have to do something like this:

.set_payload_claim("timestamp", std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count())

You can set your "sign_type" claim using something like this:

.set_header_claim("sign_type", "SIGN")

After more tests I think I figured out what happeded. Not only I should use .set_header_claim() , but also the value of time caused the issue. I decode the token generated by Python with jwt::decode(), it turns out the value from python is millisecond , but C++ version was second. Finally, below codes can work as Python version:

auto now = std::chrono::time_point_cast<std::chrono::seconds>(std::chrono::system_clock::now());
auto exp = now + std::chrono::seconds{3600};

auto milliseconds_since_epoch = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
auto milliseconds_exp = std::chrono::duration_cast<std::chrono::milliseconds>(exp.time_since_epoch()).count();

auto token_qt = jwt::create()
                     .set_payload_claim("timestamp", picojson::value(int64_t{milliseconds_since_epoch}))
                     .set_payload_claim("exp", picojson::value(int64_t{milliseconds_exp}))
                     .set_payload_claim("api_key", jwt::claim(std::string("example_of_the_api_key")))
                     .set_header_claim("sign_type", jwt::claim(std::string("SIGN")))  //必须
                     .set_header_claim("alg", jwt::claim(std::string("HS256")))  //可以不加
                     .sign(jwt::algorithm::hs256{"example_of_the_secret"});
Thalhammer commented 3 months ago

Glad you got it worked out. Keep in mind that the generated token is no longer a valid jwt token and will only work with your own code. I'd highly recommend sticking to the standards on order to not reinvent the wheel.