├── test ├── CMakeLists.txt └── testjwt.cpp ├── .gitignore ├── LICENSE ├── CMakeLists.txt ├── README.md └── jwt ├── jwt.hpp └── jwt.cpp /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include_directories(BEFORE ${PROJECT_SOURCE_DIR}) 2 | 3 | add_executable(test_jwt testjwt.cpp) 4 | add_test(jwt test_jwt) 5 | 6 | if (UNIX) 7 | target_link_libraries(test_jwt jwt crypto) 8 | elseif(WIN32) 9 | target_link_libraries(test_jwt jwt crypt32 libcrypto ws2_32) 10 | endif() -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | build/ 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | project(jwt) 3 | 4 | option(build_tests "Build tests (run as 'test' target)" ON) 5 | option(shared_lib "Build as shared library" OFF) 6 | 7 | # These flags are for binaries built by this particular CMake project (test_cppcodec, base64enc, etc.). 8 | # In your own project that uses cppcodec, you might want to specify a different standard or error level. 9 | if (MSVC) 10 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W3") 11 | else() 12 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -pedantic") 13 | endif() 14 | 15 | set(PUBLIC_HEADERS 16 | jwt/jwt.hpp 17 | ) 18 | 19 | set(PRIVATE_SOURCES 20 | jwt/jwt.cpp 21 | ) 22 | 23 | if (shared_lib) 24 | add_library(jwt OBJECT ${PUBLIC_HEADERS} ${PRIVATE_SOURCES}) 25 | else() 26 | add_library(jwt STATIC ${PUBLIC_HEADERS} ${PRIVATE_SOURCES}) 27 | endif() 28 | 29 | set_target_properties(jwt PROPERTIES LINKER_LANGUAGE CXX) 30 | 31 | if (build_tests) 32 | enable_testing() 33 | add_subdirectory(test) 34 | endif() 35 | 36 | foreach(h ${PUBLIC_HEADERS}) 37 | get_filename_component(HEADER_INCLUDE_DIRECTORY include/${h} PATH) # use DIRECTORY instead of PATH once requiring CMake 3.0 38 | install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${h} DESTINATION ${HEADER_INCLUDE_DIRECTORY} COMPONENT "headers") 39 | endforeach() -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jwt 2 | JWT in C++ 3 | 4 | Uses [nlohmann's json library](https://github.com/nlohmann/json) and SSL (not included). 5 | 6 | Example usage: 7 | ```c++ 8 | #include 9 | 10 | using namespace std; 11 | using json = nlohmann::json; 12 | 13 | string key{ "my super secret key" }; 14 | json payload{ 15 | { "user", "some_username" }, 16 | { "isAdmin", true } 17 | }; 18 | 19 | // Add some standard claims. 20 | jwt::issuedBy(payload, "my company"); // ISS claim. 21 | jwt::issuedAt(payload); // IAT claim. Defaults to current time. 22 | jwt::issuedFor(payload, {"me", "you", "everyone"}); // AUD claim. 23 | 24 | // Encode the jwt with the default algorithm (HS256). 25 | auto encodedHS256 = jwt::encode(payload, key); 26 | 27 | // Or specify an algorithm to use (HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES512). 28 | auto encodedHS384 = jwt::encode(payload, key, "HS384"); 29 | 30 | // Decode with a specified set of acceptable algorithms. 31 | auto decoded = jwt::decode(encodedHS256, key, { "HS256", "HS384" }); 32 | 33 | // Verify the standard claims. 34 | jwt::AcceptedParameters params{}; 35 | 36 | params.issuers = { "my company", "your company" }; 37 | params.audience = { "me" }; 38 | 39 | auto isValid = jwt::verify(decoded, jwt::claims::ISS | jwt::claims::IAT | jwt::claims::AUD, params); 40 | ``` -------------------------------------------------------------------------------- /jwt/jwt.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "json.hpp" 9 | 10 | namespace jwt { 11 | // 12 | // JWT encoding/decoding functions. 13 | // 14 | 15 | // Returns an empty string on failure. 16 | std::string encode(const nlohmann::json& payload, const std::string& key, const std::string& alg = ""); 17 | 18 | // Returns a null json object on failure. Doesn't throw. 19 | nlohmann::json decode(const std::string& jwt, const std::string& key, const std::set& alg = {}); 20 | 21 | // 22 | // Helper functions to add claims to a payload. 23 | // 24 | 25 | // Adds an issuer claim ("iss"). 26 | void issuedBy(nlohmann::json& payload, const std::string& issuer); 27 | 28 | // Adds a subject claim ("sub"). 29 | void subjectOf(nlohmann::json& payload, const std::string& subject); 30 | 31 | // Adds an audience claim ("aud"). 32 | void issuedFor(nlohmann::json& payload, const std::set& audience); 33 | 34 | // Adds an expiration time claim ("exp"). 35 | void expiresAt(nlohmann::json& payload, std::time_t time); 36 | 37 | // Adds a not before claim ("nbf"). 38 | void useAfter(nlohmann::json& payload, std::time_t time = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now())); 39 | 40 | // Adds an issued at claim ("iat"). 41 | void issuedAt(nlohmann::json& payload, std::time_t time = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now())); 42 | 43 | // Adds a JWT ID claim ("jti"). 44 | void identifiesAs(nlohmann::json& payload, const std::string& id); 45 | 46 | // Helper functions to verify claims made by a payload. 47 | namespace claims { 48 | enum Flags : unsigned int { 49 | NONE = 0, 50 | ISS = 1 << 0, // Issuer claim. 51 | SUB = 1 << 1, // Subject claim. 52 | AUD = 1 << 2, // Audience claim. 53 | EXP = 1 << 3, // Expiration time claim. 54 | NBF = 1 << 4, // Not before claim. 55 | IAT = 1 << 5, // Issued at claim. 56 | JTI = 1 << 6, // JWT ID claim. 57 | EVERYTHING = ISS | SUB | AUD | EXP | NBF | IAT | JTI 58 | }; 59 | } 60 | 61 | struct AcceptedParameters { 62 | std::set issuers; 63 | std::set subjects; 64 | std::set audience; 65 | std::set usedIDs; // ID's that will cause the JWT ID claim to fail. 66 | std::time_t now{ std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()) }; 67 | }; 68 | 69 | // Doesn't throw. 70 | bool verify(const nlohmann::json& payload, unsigned int claims, const AcceptedParameters& params = {}); 71 | } 72 | -------------------------------------------------------------------------------- /jwt/jwt.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "jwt.hpp" 14 | 15 | using namespace std; 16 | using namespace nlohmann; 17 | 18 | namespace jwt { 19 | namespace detail { 20 | class OnLeave { 21 | public: 22 | OnLeave() = default; 23 | 24 | ~OnLeave() { 25 | for (auto& fn : m_callbacks) { 26 | fn(); 27 | } 28 | } 29 | 30 | void add(function fn) { 31 | m_callbacks.emplace_back(move(fn)); 32 | } 33 | 34 | private: 35 | vector> m_callbacks; 36 | }; 37 | 38 | void replaceAll(string& str, const string& from, const string& to) { 39 | size_t pos = 0; 40 | 41 | while ((pos = str.find(from, pos)) != string::npos) { 42 | str.replace(pos, from.length(), to); 43 | pos += to.length(); 44 | } 45 | } 46 | 47 | string b64encode(const uint8_t* data, size_t len) { 48 | auto b64 = BIO_new(BIO_f_base64()); 49 | auto bio = BIO_new(BIO_s_mem()); 50 | 51 | bio = BIO_push(b64, bio); 52 | 53 | BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL); 54 | BIO_write(bio, data, (int)len); 55 | BIO_flush(bio); 56 | 57 | BUF_MEM* buf = nullptr; 58 | 59 | BIO_get_mem_ptr(bio, &buf); 60 | 61 | string s(buf->data, buf->length); 62 | 63 | BIO_free_all(bio); 64 | 65 | // Convert it to base64url. 66 | s = s.substr(0, s.find_last_not_of('=') + 1); 67 | replaceAll(s, "+", "-"); 68 | replaceAll(s, "/", "_"); 69 | 70 | return s; 71 | } 72 | 73 | vector b64decode(string str) { 74 | // Convert it from base64url back to normal base64. 75 | size_t padding{ 0 }; 76 | 77 | replaceAll(str, "-", "+"); 78 | replaceAll(str, "_", "/"); 79 | 80 | switch (str.length() % 4) { 81 | case 0: 82 | break; 83 | 84 | case 2: 85 | str += "=="; 86 | padding = 2; 87 | break; 88 | 89 | case 3: 90 | str += "="; 91 | padding = 1; 92 | break; 93 | 94 | default: 95 | return vector{}; 96 | } 97 | 98 | size_t len{ (str.length() * 3) / 4 - padding }; 99 | vector buf(len); 100 | 101 | auto bio = BIO_new_mem_buf((void*)str.c_str(), -1); 102 | auto b64 = BIO_new(BIO_f_base64()); 103 | 104 | bio = BIO_push(b64, bio); 105 | 106 | BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL); 107 | BIO_read(bio, buf.data(), (int)str.length()); 108 | BIO_free_all(bio); 109 | 110 | return buf; 111 | } 112 | } 113 | 114 | #define SCOPE_EXIT(x) do { onLeave.add([&]() { x; }); } while(0) 115 | 116 | string signHMAC(const string& str, const string& key, const string& alg) { 117 | const EVP_MD* evp = nullptr; 118 | 119 | if (alg == "HS256") { 120 | evp = EVP_sha256(); 121 | } 122 | else if (alg == "HS384") { 123 | evp = EVP_sha384(); 124 | } 125 | else if (alg == "HS512") { 126 | evp = EVP_sha512(); 127 | } 128 | else { 129 | return string{}; 130 | } 131 | 132 | vector out(EVP_MAX_MD_SIZE); 133 | unsigned int len = 0; 134 | 135 | HMAC(evp, key.c_str(), (int)key.length(), (const unsigned char*)str.c_str(), str.length(), out.data(), &len); 136 | 137 | return detail::b64encode(out.data(), len); 138 | } 139 | 140 | string signPEM(const string& str, const string& key, const string& alg) { 141 | detail::OnLeave onLeave{}; 142 | const EVP_MD* evp = nullptr; 143 | 144 | if (alg == "RS256") { 145 | evp = EVP_sha256(); 146 | } 147 | else if (alg == "RS384") { 148 | evp = EVP_sha384(); 149 | } 150 | else if (alg == "RS512") { 151 | evp = EVP_sha512(); 152 | } 153 | else if (alg == "ES256") { 154 | evp = EVP_sha256(); 155 | } 156 | else if (alg == "ES384") { 157 | evp = EVP_sha384(); 158 | } 159 | else if (alg == "ES512") { 160 | evp = EVP_sha512(); 161 | } 162 | else { 163 | return {}; 164 | } 165 | 166 | auto bufkey = BIO_new_mem_buf((void*)key.c_str(), (int)key.length()); 167 | SCOPE_EXIT(if (bufkey != nullptr) BIO_free(bufkey)); 168 | 169 | if (bufkey == nullptr) { 170 | return {}; 171 | } 172 | 173 | // Use OpenSSL's default passphrase callbacks if needed. 174 | auto pkey = PEM_read_bio_PrivateKey(bufkey, nullptr, nullptr, nullptr); 175 | SCOPE_EXIT(if (pkey != nullptr) EVP_PKEY_free(pkey)); 176 | 177 | if (pkey == nullptr) { 178 | return {}; 179 | } 180 | 181 | auto mdctx = EVP_MD_CTX_create(); 182 | SCOPE_EXIT(if (mdctx != nullptr) EVP_MD_CTX_destroy(mdctx)); 183 | 184 | if (mdctx == nullptr) { 185 | return {}; 186 | } 187 | 188 | // Initialize the digest sign operation. 189 | if (EVP_DigestSignInit(mdctx, nullptr, evp, nullptr, pkey) != 1) { 190 | return {}; 191 | } 192 | 193 | // Update the digest sign with the message. 194 | if (EVP_DigestSignUpdate(mdctx, str.c_str(), str.length()) != 1) { 195 | return {}; 196 | } 197 | 198 | // Determin the size of the finalized digest sign. 199 | size_t siglen = 0; 200 | 201 | if (EVP_DigestSignFinal(mdctx, nullptr, &siglen) != 1) { 202 | return {}; 203 | } 204 | 205 | // Finalize it. 206 | vector sig(siglen); 207 | 208 | if (EVP_DigestSignFinal(mdctx, sig.data(), &siglen) != 1) { 209 | return {}; 210 | } 211 | 212 | // For RSA, we are done. 213 | return detail::b64encode(sig.data(), siglen); 214 | } 215 | 216 | bool verifyPEM(const string& str, const string& b64sig, const string& key, const string& alg) { 217 | detail::OnLeave onLeave{}; 218 | const EVP_MD* evp = nullptr; 219 | 220 | if (alg == "RS256") { 221 | evp = EVP_sha256(); 222 | } 223 | else if (alg == "RS384") { 224 | evp = EVP_sha384(); 225 | } 226 | else if (alg == "RS512") { 227 | evp = EVP_sha512(); 228 | } 229 | else if (alg == "ES256") { 230 | evp = EVP_sha256(); 231 | } 232 | else if (alg == "ES384") { 233 | evp = EVP_sha384(); 234 | } 235 | else if (alg == "ES512") { 236 | evp = EVP_sha512(); 237 | } 238 | else { 239 | return false; 240 | } 241 | 242 | auto sig = detail::b64decode(b64sig); 243 | auto siglen = sig.size(); 244 | 245 | if (sig.empty()) { 246 | return false; 247 | } 248 | 249 | auto bufkey = BIO_new_mem_buf((void*)key.c_str(), (int)key.length()); 250 | SCOPE_EXIT(if (bufkey != nullptr) BIO_free(bufkey)); 251 | 252 | if (bufkey == nullptr) { 253 | return false; 254 | } 255 | 256 | // Use OpenSSL's default passphrase callbacks if needed. 257 | auto pkey = PEM_read_bio_PUBKEY(bufkey, nullptr, nullptr, nullptr); 258 | SCOPE_EXIT(if (pkey != nullptr) EVP_PKEY_free(pkey)); 259 | 260 | if (pkey == nullptr) { 261 | return false; 262 | } 263 | 264 | auto mdctx = EVP_MD_CTX_create(); 265 | SCOPE_EXIT(if (mdctx != nullptr) EVP_MD_CTX_destroy(mdctx)); 266 | 267 | if (EVP_DigestVerifyInit(mdctx, nullptr, evp, nullptr, pkey) != 1) { 268 | return false; 269 | } 270 | 271 | if (EVP_DigestVerifyUpdate(mdctx, str.c_str(), str.length()) != 1) { 272 | return false; 273 | } 274 | 275 | if (EVP_DigestVerifyFinal(mdctx, sig.data(), siglen) != 1) { 276 | return false; 277 | } 278 | 279 | return true; 280 | } 281 | 282 | string encode(const json& payload, const string& key, const string& alg) { 283 | // Create a JWT header defaulting to the HS256 alg if none is supplied. 284 | json header{ 285 | {"typ", "JWT"}, 286 | {"alg", alg.empty() ? "HS256" : alg } 287 | }; 288 | auto headerStr = header.dump(); 289 | auto encodedHeader = detail::b64encode((const uint8_t*)headerStr.c_str(), headerStr.length()); 290 | 291 | // Encode the payload. 292 | auto payloadStr = payload.dump(); 293 | auto encodedPayload = detail::b64encode((const uint8_t*)payloadStr.c_str(), payloadStr.length()); 294 | 295 | // Sign it and return the final JWT. 296 | auto encodedToken = encodedHeader + "." + encodedPayload; 297 | const string& theAlg = header["alg"]; 298 | string signature{}; 299 | 300 | if (theAlg == "none") { 301 | // Nothing to sign. 302 | } 303 | else if (theAlg.find("HS") != string::npos) { 304 | signature = signHMAC(encodedToken, key, theAlg); 305 | } 306 | else { 307 | signature = signPEM(encodedToken, key, theAlg); 308 | } 309 | 310 | if (theAlg != "none" && signature.empty()) { 311 | return {}; 312 | } 313 | 314 | return encodedToken + "." + signature; 315 | } 316 | 317 | json decode(const string& jwt, const string& key, const set& alg) { 318 | try { 319 | if (jwt.empty()) { 320 | return {}; 321 | } 322 | 323 | // Make sure the jwt we recieve looks like a jwt. 324 | auto firstPeriod = jwt.find_first_of('.'); 325 | auto secondPeriod = jwt.find_first_of('.', firstPeriod + 1); 326 | 327 | if (firstPeriod == string::npos || secondPeriod == string::npos) { 328 | return {}; 329 | } 330 | 331 | // Decode the header so we can get the alg used by the jwt. 332 | auto decodedHeader = detail::b64decode(jwt.substr(0, firstPeriod)); 333 | string decodedHeaderStr{ decodedHeader.begin(), decodedHeader.end() }; 334 | auto header = json::parse(decodedHeaderStr.c_str()); 335 | const string& theAlg = header["alg"]; 336 | 337 | // Make sure no key is supplied if the alg is none. 338 | if (theAlg == "none" && !key.empty()) { 339 | return {}; 340 | } 341 | 342 | // Make sure the alg supplied is one we expect. 343 | if (alg.count(theAlg) == 0 && !alg.empty()) { 344 | return {}; 345 | } 346 | 347 | auto encodedToken = jwt.substr(0, secondPeriod); 348 | auto signature = jwt.substr(secondPeriod + 1); 349 | 350 | // Verify the signature. 351 | if (theAlg == "none") { 352 | // Nothing to do, no verification needed. 353 | } 354 | else if (theAlg.find("HS") != string::npos) { 355 | auto calculatedSignature = signHMAC(encodedToken, key, theAlg); 356 | 357 | if (signature != calculatedSignature || calculatedSignature.empty()) { 358 | return {}; 359 | } 360 | } 361 | else { 362 | if (!verifyPEM(encodedToken, signature, key, theAlg)) { 363 | return {}; 364 | } 365 | } 366 | 367 | // Decode the payload since the jwt has been verified. 368 | auto decodedPayload = detail::b64decode(jwt.substr(firstPeriod + 1, secondPeriod - firstPeriod - 1)); 369 | string decodedPayloadStr{ decodedPayload.begin(), decodedPayload.end() }; 370 | auto payload = json::parse(decodedPayloadStr.c_str()); 371 | 372 | return payload; 373 | } 374 | catch (...) { 375 | return {}; 376 | } 377 | } 378 | 379 | void issuedBy(json& payload, const string& issuer) { 380 | payload["iss"] = issuer; 381 | } 382 | 383 | void subjectOf(json& payload, const string& subject) { 384 | payload["sub"] = subject; 385 | } 386 | 387 | void issuedFor(json& payload, const set& audience) { 388 | if (audience.size() == 1) { 389 | payload["aud"] = (*audience.cbegin()); 390 | } 391 | else { 392 | for (const auto& member : audience) { 393 | payload["aud"].push_back(member); 394 | } 395 | } 396 | } 397 | 398 | void expiresAt(json& payload, time_t time) { 399 | payload["exp"] = time; 400 | } 401 | 402 | void useAfter(json& payload, time_t time) { 403 | payload["nbf"] = time; 404 | } 405 | 406 | void issuedAt(json& payload, time_t time) { 407 | payload["iat"] = time; 408 | } 409 | 410 | void identifiesAs(json& payload, const string& id) { 411 | payload["jti"] = id; 412 | } 413 | 414 | bool verify(const json& payload, unsigned int claims, const AcceptedParameters& params) { 415 | try { 416 | // json::value<> can only be used on objects, and payloads should always 417 | // be objects. 418 | if (!payload.is_object()) { 419 | return false; 420 | } 421 | 422 | bool isValid{ true }; 423 | 424 | // Check the issuer claim. 425 | if ((claims & claims::ISS) != 0) { 426 | auto iss = payload.at("iss").get(); 427 | 428 | if (params.issuers.count(iss) == 0) { 429 | isValid = false; 430 | } 431 | } 432 | 433 | // Check the subject claim. 434 | if ((claims & claims::SUB) != 0) { 435 | auto sub = payload.at("sub").get(); 436 | 437 | if (params.subjects.count(sub) == 0) { 438 | isValid = false; 439 | } 440 | } 441 | 442 | // Check the audience claim. 443 | if ((claims & claims::AUD) != 0) { 444 | auto aud = payload.at("aud"); 445 | 446 | if (aud.is_array()) { 447 | auto audience = aud.get>(); 448 | vector intersection{}; 449 | 450 | set_intersection(audience.begin(), audience.end(), 451 | params.audience.begin(), params.audience.end(), 452 | back_inserter(intersection)); 453 | 454 | if (intersection.empty()) { 455 | isValid = false; 456 | } 457 | } 458 | else { 459 | if (params.audience.count(aud) == 0) { 460 | isValid = false; 461 | } 462 | } 463 | } 464 | 465 | // Check the expires at claim. 466 | if ((claims & claims::EXP) != 0) { 467 | auto exp = payload.at("exp").get(); 468 | 469 | if (params.now > exp) { 470 | isValid = false; 471 | } 472 | } 473 | 474 | // Check the not before claim. 475 | if ((claims & claims::NBF) != 0) { 476 | auto nbf = payload.at("nbf").get(); 477 | 478 | if (params.now < nbf) { 479 | isValid = false; 480 | } 481 | } 482 | 483 | // Issued at claim is just for checking the age of the jwt, just check 484 | // that there is an iat entry with a resonable value. 485 | if ((claims & claims::IAT) != 0) { 486 | auto iat = payload.at("iat"); 487 | 488 | if (!iat.is_number_unsigned()) { 489 | isValid = false; 490 | } 491 | } 492 | 493 | // Check the JWT ID claim. 494 | if ((claims & claims::JTI) != 0) { 495 | auto jti = payload.at("jti").get(); 496 | 497 | if (params.usedIDs.count(jti) != 0) { 498 | isValid = false; 499 | } 500 | } 501 | 502 | return isValid; 503 | } 504 | catch (...) { 505 | // If there was a problem verifying something, then it seems resonable 506 | // to return false. 507 | return false; 508 | } 509 | } 510 | } -------------------------------------------------------------------------------- /test/testjwt.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define CATCH_CONFIG_MAIN 4 | #include "catch.hpp" 5 | #include "jwt/jwt.hpp" 6 | #include "jwt/json.hpp" 7 | 8 | using namespace std; 9 | using namespace nlohmann; 10 | 11 | SCENARIO("JWT's can be verified") { 12 | string key{ "secret" }; 13 | 14 | GIVEN("A JWT with a non-expired exp field") { 15 | json payload{}; 16 | auto exp = chrono::system_clock::now() + chrono::minutes(5); 17 | 18 | jwt::expiresAt(payload, chrono::system_clock::to_time_t(exp)); 19 | 20 | auto encoded = jwt::encode(payload, key); 21 | 22 | WHEN("it is decoded and verified") { 23 | auto decoded = jwt::decode(encoded, key); 24 | auto isValid = jwt::verify(decoded, jwt::claims::EXP); 25 | 26 | THEN("it is valid") { 27 | REQUIRE(isValid == true); 28 | } 29 | } 30 | } 31 | 32 | GIVEN("A JWT with an expired exp field") { 33 | json payload{}; 34 | auto exp = chrono::system_clock::now() - chrono::minutes(5); 35 | 36 | jwt::expiresAt(payload, chrono::system_clock::to_time_t(exp)); 37 | 38 | auto encoded = jwt::encode(payload, key); 39 | 40 | WHEN("it is decoded and verified") { 41 | auto decoded = jwt::decode(encoded, key); 42 | auto isValid = jwt::verify(decoded, jwt::claims::EXP); 43 | 44 | THEN("it is invalid") { 45 | REQUIRE(isValid == false); 46 | } 47 | } 48 | } 49 | 50 | GIVEN("A JWT with an in-time nbf field") { 51 | json payload{}; 52 | auto nbf = chrono::system_clock::now() - chrono::minutes(5); 53 | 54 | jwt::useAfter(payload, chrono::system_clock::to_time_t(nbf)); 55 | 56 | auto encoded = jwt::encode(payload, key); 57 | 58 | WHEN("it is decoded and verified") { 59 | auto decoded = jwt::decode(encoded, key); 60 | auto isValid = jwt::verify(decoded, jwt::claims::NBF); 61 | 62 | THEN("it is valid") { 63 | REQUIRE(isValid == true); 64 | } 65 | } 66 | } 67 | 68 | GIVEN("A JWT with a not in-time nbf field") { 69 | json payload{}; 70 | auto nbf = chrono::system_clock::now() + chrono::minutes(5); 71 | 72 | jwt::useAfter(payload, chrono::system_clock::to_time_t(nbf)); 73 | 74 | auto encoded = jwt::encode(payload, key); 75 | 76 | WHEN("it is decoded and verified") { 77 | auto decoded = jwt::decode(encoded, key); 78 | auto isValid = jwt::verify(decoded, jwt::claims::NBF); 79 | 80 | THEN("it is invalid") { 81 | REQUIRE(isValid == false); 82 | } 83 | } 84 | } 85 | 86 | GIVEN("A JWT with a valid nbf and exp field") { 87 | json payload{}; 88 | auto nbf = chrono::system_clock::now() - chrono::minutes(5); 89 | auto exp = chrono::system_clock::now() + chrono::minutes(5); 90 | 91 | jwt::useAfter(payload, chrono::system_clock::to_time_t(nbf)); 92 | jwt::expiresAt(payload, chrono::system_clock::to_time_t(exp)); 93 | 94 | auto encoded = jwt::encode(payload, key); 95 | 96 | WHEN("it is decoded and verified") { 97 | auto decoded = jwt::decode(encoded, key); 98 | auto isValid = jwt::verify(decoded, jwt::claims::NBF | jwt::claims::EXP); 99 | 100 | THEN("it is valid") { 101 | REQUIRE(isValid == true); 102 | } 103 | } 104 | } 105 | 106 | GIVEN("A JWT with a valid nbf but invalid exp field") { 107 | json payload{}; 108 | auto nbf = chrono::system_clock::now() - chrono::minutes(10); 109 | auto exp = nbf - chrono::minutes(5); 110 | 111 | jwt::useAfter(payload, chrono::system_clock::to_time_t(nbf)); 112 | jwt::expiresAt(payload, chrono::system_clock::to_time_t(exp)); 113 | 114 | auto encoded = jwt::encode(payload, key); 115 | 116 | WHEN("it is decoded and verified") { 117 | auto decoded = jwt::decode(encoded, key); 118 | auto isValid = jwt::verify(decoded, jwt::claims::NBF | jwt::claims::EXP); 119 | 120 | THEN("it is invalid") { 121 | REQUIRE(isValid == false); 122 | } 123 | } 124 | } 125 | 126 | GIVEN("A JWT with an invalid nbf but valid exp field") { 127 | json payload{}; 128 | auto nbf = chrono::system_clock::now() + chrono::minutes(10); 129 | auto exp = chrono::system_clock::now() + chrono::minutes(15); 130 | 131 | jwt::useAfter(payload, chrono::system_clock::to_time_t(nbf)); 132 | jwt::expiresAt(payload, chrono::system_clock::to_time_t(exp)); 133 | 134 | auto encoded = jwt::encode(payload, key); 135 | 136 | WHEN("it is decoded and verified") { 137 | auto decoded = jwt::decode(encoded, key); 138 | auto isValid = jwt::verify(decoded, jwt::claims::NBF | jwt::claims::EXP); 139 | 140 | THEN("it is invalid") { 141 | REQUIRE(isValid == false); 142 | } 143 | } 144 | } 145 | 146 | GIVEN("A JWT with no nbf or exp field") { 147 | json payload{}; 148 | auto encoded = jwt::encode(payload, key); 149 | 150 | WHEN("it is decoded and verified for nbf and exp") { 151 | auto decoded = jwt::decode(encoded, key); 152 | auto isValid = jwt::verify(decoded, jwt::claims::NBF | jwt::claims::EXP); 153 | 154 | THEN("it is invalid") { 155 | REQUIRE(isValid == false); 156 | } 157 | } 158 | } 159 | 160 | GIVEN("A JWT with an invalid iat field") { 161 | json payload{ 162 | {"iat", "asjdflka"} 163 | }; 164 | auto encoded = jwt::encode(payload, key); 165 | 166 | WHEN("it is decoded and verified for iat") { 167 | auto decoded = jwt::decode(encoded, key); 168 | auto isValid = jwt::verify(decoded, jwt::claims::IAT); 169 | 170 | THEN("it is invalid") { 171 | REQUIRE(isValid == false); 172 | } 173 | } 174 | } 175 | 176 | GIVEN("A JWT with lots of claims") { 177 | json payload{}; 178 | 179 | jwt::issuedBy(payload, "cursey"); 180 | jwt::subjectOf(payload, "auth"); 181 | jwt::issuedFor(payload, { "me", "you", "everyone" }); 182 | jwt::expiresAt(payload, chrono::system_clock::to_time_t(chrono::system_clock::now() + chrono::hours(24))); 183 | jwt::useAfter(payload, chrono::system_clock::to_time_t(chrono::system_clock::now() - chrono::hours(1))); 184 | jwt::issuedAt(payload); 185 | jwt::identifiesAs(payload, "123abc"); 186 | 187 | auto encoded = jwt::encode(payload, key); 188 | 189 | WHEN("it is decoded and verified for everything without extensive params") { 190 | auto decoded = jwt::decode(encoded, key); 191 | auto isValid = jwt::verify(decoded, jwt::claims::EVERYTHING); 192 | 193 | THEN("it is invalid") { 194 | REQUIRE(isValid == false); 195 | } 196 | } 197 | 198 | WHEN("it is decoded and verified for everything with extensive params") { 199 | jwt::AcceptedParameters params{}; 200 | 201 | params.issuers = { "cursey" }; 202 | params.subjects = { "auth", "post", "delete" }; 203 | params.audience = { "me" }; 204 | params.usedIDs = { "123ab", "12abc", "abc123" }; 205 | 206 | auto decoded = jwt::decode(encoded, key); 207 | auto isValid = jwt::verify(decoded, jwt::claims::EVERYTHING, params); 208 | 209 | THEN("it is valid") { 210 | REQUIRE(isValid == true); 211 | } 212 | } 213 | } 214 | } 215 | 216 | SCENARIO("Invalid signatures cause decoding to fail") { 217 | string hsKey{ "secret" }; 218 | auto rsPublicKey = R"( 219 | -----BEGIN PUBLIC KEY----- 220 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8kGa1pSjbSYZVebtTRBLxBz5H 221 | 4i2p/llLCrEeQhta5kaQu/RnvuER4W8oDH3+3iuIYW4VQAzyqFpwuzjkDI+17t5t 222 | 0tyazyZ8JXw+KgXTxldMPEL95+qVhgXvwtihXC1c5oGbRlEDvDF6Sa53rcFVsYJ4 223 | ehde/zUxo6UvS7UrBQIDAQAB 224 | -----END PUBLIC KEY----- 225 | )"; 226 | auto esPublicKey = R"( 227 | -----BEGIN PUBLIC KEY----- 228 | MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAR/acP0tFaeapRIWFpPsApcRYiFc5 229 | OvmzlRcJrINzShRBHZKufJ6/A2+XNquYETqpnHYwXFKlo/Ne0Zs8pKfz0EwAP6/z 230 | hdCHLPADaPT8ghKSn4knIKTcUrj8apbtkiEZ+7wtltyHEah825dTCSeLspOLfDTN 231 | 4S1lJWUVZN1moK4h0aI= 232 | -----END PUBLIC KEY----- 233 | )"; 234 | 235 | GIVEN("An HS256 encoded token with an invalid signature") { 236 | string encodedToken{ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9" }; 237 | auto encoded = encodedToken + ".aW52YWxpZA"; 238 | 239 | WHEN("it is decoded") { 240 | auto decoded = jwt::decode(encoded, hsKey); 241 | 242 | THEN("it returns null") { 243 | REQUIRE(decoded == nullptr); 244 | } 245 | } 246 | } 247 | 248 | GIVEN("An HS384 encoded token with an invalid signature") { 249 | string encodedToken{ "eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9" }; 250 | auto encoded = encodedToken + ".aW52YWxpZA"; 251 | 252 | WHEN("it is decoded") { 253 | auto decoded = jwt::decode(encoded, hsKey); 254 | 255 | THEN("it returns null") { 256 | REQUIRE(decoded == nullptr); 257 | } 258 | } 259 | } 260 | 261 | GIVEN("An HS512 encoded token with an invalid signature") { 262 | string encodedToken{ "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9" }; 263 | auto encoded = encodedToken + ".aW52YWxpZA"; 264 | 265 | WHEN("it is decoded") { 266 | auto decoded = jwt::decode(encoded, hsKey); 267 | 268 | THEN("it returns null") { 269 | REQUIRE(decoded == nullptr); 270 | } 271 | } 272 | } 273 | 274 | GIVEN("An RS256 encoded token with an invalid signature") { 275 | string encodedToken{ "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9" }; 276 | auto encoded = encodedToken + ".aW52YWxpZA"; 277 | 278 | WHEN("it is decoded") { 279 | auto decoded = jwt::decode(encoded, rsPublicKey); 280 | 281 | THEN("it returns null") { 282 | REQUIRE(decoded == nullptr); 283 | } 284 | } 285 | } 286 | 287 | GIVEN("An RS384 encoded token with an invalid signature") { 288 | string encodedToken{ "eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9" }; 289 | auto encoded = encodedToken + ".aW52YWxpZA"; 290 | 291 | WHEN("it is decoded") { 292 | auto decoded = jwt::decode(encoded, rsPublicKey); 293 | 294 | THEN("it returns null") { 295 | REQUIRE(decoded == nullptr); 296 | } 297 | } 298 | } 299 | 300 | GIVEN("An RS512 encoded token with an invalid signature") { 301 | string encodedToken{ "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9" }; 302 | auto encoded = encodedToken + ".aW52YWxpZA"; 303 | 304 | WHEN("it is decoded") { 305 | auto decoded = jwt::decode(encoded, rsPublicKey); 306 | 307 | THEN("it returns null") { 308 | REQUIRE(decoded == nullptr); 309 | } 310 | } 311 | } 312 | 313 | 314 | GIVEN("An ES256 encoded token with an invalid signature") { 315 | string encodedToken{ "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9" }; 316 | auto encoded = encodedToken + ".aW52YWxpZA"; 317 | 318 | WHEN("it is decoded") { 319 | auto decoded = jwt::decode(encoded, esPublicKey); 320 | 321 | THEN("it returns null") { 322 | REQUIRE(decoded == nullptr); 323 | } 324 | } 325 | } 326 | 327 | GIVEN("An ES384 encoded token with an invalid signature") { 328 | string encodedToken{ "eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9" }; 329 | auto encoded = encodedToken + ".aW52YWxpZA"; 330 | 331 | WHEN("it is decoded") { 332 | auto decoded = jwt::decode(encoded, esPublicKey); 333 | 334 | THEN("it returns null") { 335 | REQUIRE(decoded == nullptr); 336 | } 337 | } 338 | } 339 | 340 | GIVEN("An ES512 encoded token with an invalid signature") { 341 | string encodedToken{ "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9" }; 342 | auto encoded = encodedToken + ".aW52YWxpZA"; 343 | 344 | WHEN("it is decoded") { 345 | auto decoded = jwt::decode(encoded, esPublicKey); 346 | 347 | THEN("it returns null") { 348 | REQUIRE(decoded == nullptr); 349 | } 350 | } 351 | } 352 | } 353 | 354 | SCENARIO("Invalid parameters cause decoding to fail") { 355 | string key{ "secret" }; 356 | auto payload = R"( 357 | { 358 | "sub": "1234567890", 359 | "name": "John Doe", 360 | "admin": true 361 | } 362 | )"_json; 363 | 364 | GIVEN("some payload") { 365 | WHEN("encoded with an invalid algorithm") { 366 | auto encoded = jwt::encode(payload, key, { "HK256" }); 367 | 368 | THEN("it returns an empty string") { 369 | REQUIRE(encoded.empty()); 370 | } 371 | } 372 | } 373 | 374 | GIVEN("An empty string") { 375 | string encoded{}; 376 | 377 | WHEN("it is decoded") { 378 | auto decoded = jwt::decode(encoded, key); 379 | 380 | THEN("it fails to decode by returning nullptr") { 381 | REQUIRE(decoded == nullptr); 382 | } 383 | } 384 | } 385 | } 386 | 387 | SCENARIO("JWT's can be encoded and decoded using no alg") { 388 | auto payload = R"( 389 | { 390 | "sub": "1234567890", 391 | "name": "John Doe", 392 | "admin": true 393 | } 394 | )"_json; 395 | 396 | GIVEN("An encoded payload") { 397 | auto encoded = jwt::encode(payload, "", "none"); 398 | 399 | WHEN("it is decoded") { 400 | auto decoded = jwt::decode(encoded, "", { "none" }); 401 | 402 | THEN("it equals the payload") { 403 | REQUIRE(decoded == payload); 404 | } 405 | } 406 | 407 | WHEN("it is auto decoded with no algorithms specified") { 408 | auto decoded = jwt::decode(encoded, ""); 409 | 410 | THEN("the algorithm is determined by the token and properly decoded") { 411 | REQUIRE(decoded == payload); 412 | } 413 | } 414 | 415 | WHEN("it is decoded with the wrong algorithms specified") { 416 | auto decoded = jwt::decode(encoded, "", { "HS384" }); 417 | 418 | THEN("it returns null") { 419 | REQUIRE(decoded == nullptr); 420 | } 421 | } 422 | 423 | WHEN("it is decoded with a key") { 424 | auto decoded = jwt::decode(encoded, "secret"); 425 | 426 | THEN("it returns null") { 427 | REQUIRE(decoded == nullptr); 428 | } 429 | } 430 | 431 | WHEN("it is decoded with a key and 'none' specified") { 432 | auto decoded = jwt::decode(encoded, "secret", { "none" }); 433 | 434 | THEN("it returns null") { 435 | REQUIRE(decoded == nullptr); 436 | } 437 | } 438 | } 439 | } 440 | 441 | SCENARIO("JWT's can be encoded and decoded using HS256") { 442 | string key{ "secret" }; 443 | auto payload = R"( 444 | { 445 | "sub": "1234567890", 446 | "name": "John Doe", 447 | "admin": true 448 | } 449 | )"_json; 450 | 451 | GIVEN("An encoded payload") { 452 | auto encoded = jwt::encode(payload, key, "HS256"); 453 | 454 | WHEN("it is decoded") { 455 | auto decoded = jwt::decode(encoded, key, { "HS256" }); 456 | 457 | THEN("it equals the payload") { 458 | REQUIRE(decoded == payload); 459 | } 460 | } 461 | 462 | WHEN("it is auto decoded with no algorithms specified") { 463 | auto decoded = jwt::decode(encoded, key); 464 | 465 | THEN("the algorithm is determined by the token and properly decoded") { 466 | REQUIRE(decoded == payload); 467 | } 468 | } 469 | 470 | WHEN("it is decoded with the wrong algorithms specified") { 471 | auto decoded = jwt::decode(encoded, key, { "HS384" }); 472 | 473 | THEN("it returns null") { 474 | REQUIRE(decoded == nullptr); 475 | } 476 | } 477 | } 478 | } 479 | 480 | SCENARIO("JWT's can be encoded and decoded using HS384") { 481 | string key{ "secret" }; 482 | auto payload = R"( 483 | { 484 | "sub": "1234567890", 485 | "name": "John Doe", 486 | "admin": true 487 | } 488 | )"_json; 489 | 490 | GIVEN("An encoded payload") { 491 | auto encoded = jwt::encode(payload, key, "HS384"); 492 | 493 | WHEN("it is decoded") { 494 | auto decoded = jwt::decode(encoded, key, { "HS384" }); 495 | 496 | THEN("it equals the payload") { 497 | REQUIRE(decoded == payload); 498 | } 499 | } 500 | 501 | WHEN("it is auto decoded with no algorithms specified") { 502 | auto decoded = jwt::decode(encoded, key); 503 | 504 | THEN("the algorithm is determined by the token and properly decoded") { 505 | REQUIRE(decoded == payload); 506 | } 507 | } 508 | 509 | WHEN("it is decoded with the wrong algorithms specified") { 510 | auto decoded = jwt::decode(encoded, key, { "HS512" }); 511 | 512 | THEN("it returns null") { 513 | REQUIRE(decoded == nullptr); 514 | } 515 | } 516 | } 517 | } 518 | 519 | SCENARIO("JWT's can be encoded and decoded using HS512") { 520 | string key{ "secret" }; 521 | auto payload = R"( 522 | { 523 | "sub": "1234567890", 524 | "name": "John Doe", 525 | "admin": true 526 | } 527 | )"_json; 528 | 529 | GIVEN("An encoded payload") { 530 | auto encoded = jwt::encode(payload, key, "HS512"); 531 | 532 | WHEN("it is decoded") { 533 | auto decoded = jwt::decode(encoded, key, { "HS512" }); 534 | 535 | THEN("it equals the payload") { 536 | REQUIRE(decoded == payload); 537 | } 538 | } 539 | 540 | WHEN("it is auto decoded with no algorithms specified") { 541 | auto decoded = jwt::decode(encoded, key); 542 | 543 | THEN("the algorithm is determined by the token and properly decoded") { 544 | REQUIRE(decoded == payload); 545 | } 546 | } 547 | 548 | WHEN("it is decoded with the wrong algorithms specified") { 549 | auto decoded = jwt::decode(encoded, key, { "HS256" }); 550 | 551 | THEN("it returns null") { 552 | REQUIRE(decoded == nullptr); 553 | } 554 | } 555 | } 556 | } 557 | 558 | SCENARIO("JWT's can be encoded and decoded using RS256") { 559 | auto payload = R"( 560 | { 561 | "sub": "1234567890", 562 | "name": "John Doe", 563 | "admin": true 564 | } 565 | )"_json; 566 | auto privateKey = R"( 567 | -----BEGIN RSA PRIVATE KEY----- 568 | MIICXAIBAAKBgQC8kGa1pSjbSYZVebtTRBLxBz5H4i2p/llLCrEeQhta5kaQu/Rn 569 | vuER4W8oDH3+3iuIYW4VQAzyqFpwuzjkDI+17t5t0tyazyZ8JXw+KgXTxldMPEL9 570 | 5+qVhgXvwtihXC1c5oGbRlEDvDF6Sa53rcFVsYJ4ehde/zUxo6UvS7UrBQIDAQAB 571 | AoGAb/MXV46XxCFRxNuB8LyAtmLDgi/xRnTAlMHjSACddwkyKem8//8eZtw9fzxz 572 | bWZ/1/doQOuHBGYZU8aDzzj59FZ78dyzNFoF91hbvZKkg+6wGyd/LrGVEB+Xre0J 573 | Nil0GReM2AHDNZUYRv+HYJPIOrB0CRczLQsgFJ8K6aAD6F0CQQDzbpjYdx10qgK1 574 | cP59UHiHjPZYC0loEsk7s+hUmT3QHerAQJMZWC11Qrn2N+ybwwNblDKv+s5qgMQ5 575 | 5tNoQ9IfAkEAxkyffU6ythpg/H0Ixe1I2rd0GbF05biIzO/i77Det3n4YsJVlDck 576 | ZkcvY3SK2iRIL4c9yY6hlIhs+K9wXTtGWwJBAO9Dskl48mO7woPR9uD22jDpNSwe 577 | k90OMepTjzSvlhjbfuPN1IdhqvSJTDychRwn1kIJ7LQZgQ8fVz9OCFZ/6qMCQGOb 578 | qaGwHmUK6xzpUbbacnYrIM6nLSkXgOAwv7XXCojvY614ILTK3iXiLBOxPu5Eu13k 579 | eUz9sHyD6vkgZzjtxXECQAkp4Xerf5TGfQXGXhxIX52yH+N2LtujCdkQZjXAsGdm 580 | B2zNzvrlgRmgBrklMTrMYgm1NPcW+bRLGcwgW2PTvNM= 581 | -----END RSA PRIVATE KEY----- 582 | )"; 583 | auto publicKey = R"( 584 | -----BEGIN PUBLIC KEY----- 585 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8kGa1pSjbSYZVebtTRBLxBz5H 586 | 4i2p/llLCrEeQhta5kaQu/RnvuER4W8oDH3+3iuIYW4VQAzyqFpwuzjkDI+17t5t 587 | 0tyazyZ8JXw+KgXTxldMPEL95+qVhgXvwtihXC1c5oGbRlEDvDF6Sa53rcFVsYJ4 588 | ehde/zUxo6UvS7UrBQIDAQAB 589 | -----END PUBLIC KEY----- 590 | )"; 591 | 592 | GIVEN("An encoded payload using a private key") { 593 | auto encoded = jwt::encode(payload, privateKey, "RS256"); 594 | 595 | WHEN("it is decoded using a public key") { 596 | auto decoded = jwt::decode(encoded, publicKey, { "RS256" }); 597 | 598 | THEN("it equals the payload") { 599 | REQUIRE(decoded == payload); 600 | } 601 | } 602 | 603 | WHEN("it is auto decoded with no algorithms specified") { 604 | auto decoded = jwt::decode(encoded, publicKey); 605 | 606 | THEN("the algorithm is determined by the token and properly decoded") { 607 | REQUIRE(decoded == payload); 608 | } 609 | } 610 | 611 | WHEN("it is decoded with the wrong algorithms specified") { 612 | auto decoded = jwt::decode(encoded, publicKey, { "RS384" }); 613 | 614 | THEN("it returns null") { 615 | REQUIRE(decoded == nullptr); 616 | } 617 | } 618 | } 619 | } 620 | 621 | SCENARIO("JWT's can be encoded and decoded using RS384") { 622 | auto payload = R"( 623 | { 624 | "sub": "1234567890", 625 | "name": "John Doe", 626 | "admin": true 627 | } 628 | )"_json; 629 | auto privateKey = R"( 630 | -----BEGIN RSA PRIVATE KEY----- 631 | MIICXAIBAAKBgQC8kGa1pSjbSYZVebtTRBLxBz5H4i2p/llLCrEeQhta5kaQu/Rn 632 | vuER4W8oDH3+3iuIYW4VQAzyqFpwuzjkDI+17t5t0tyazyZ8JXw+KgXTxldMPEL9 633 | 5+qVhgXvwtihXC1c5oGbRlEDvDF6Sa53rcFVsYJ4ehde/zUxo6UvS7UrBQIDAQAB 634 | AoGAb/MXV46XxCFRxNuB8LyAtmLDgi/xRnTAlMHjSACddwkyKem8//8eZtw9fzxz 635 | bWZ/1/doQOuHBGYZU8aDzzj59FZ78dyzNFoF91hbvZKkg+6wGyd/LrGVEB+Xre0J 636 | Nil0GReM2AHDNZUYRv+HYJPIOrB0CRczLQsgFJ8K6aAD6F0CQQDzbpjYdx10qgK1 637 | cP59UHiHjPZYC0loEsk7s+hUmT3QHerAQJMZWC11Qrn2N+ybwwNblDKv+s5qgMQ5 638 | 5tNoQ9IfAkEAxkyffU6ythpg/H0Ixe1I2rd0GbF05biIzO/i77Det3n4YsJVlDck 639 | ZkcvY3SK2iRIL4c9yY6hlIhs+K9wXTtGWwJBAO9Dskl48mO7woPR9uD22jDpNSwe 640 | k90OMepTjzSvlhjbfuPN1IdhqvSJTDychRwn1kIJ7LQZgQ8fVz9OCFZ/6qMCQGOb 641 | qaGwHmUK6xzpUbbacnYrIM6nLSkXgOAwv7XXCojvY614ILTK3iXiLBOxPu5Eu13k 642 | eUz9sHyD6vkgZzjtxXECQAkp4Xerf5TGfQXGXhxIX52yH+N2LtujCdkQZjXAsGdm 643 | B2zNzvrlgRmgBrklMTrMYgm1NPcW+bRLGcwgW2PTvNM= 644 | -----END RSA PRIVATE KEY----- 645 | )"; 646 | auto publicKey = R"( 647 | -----BEGIN PUBLIC KEY----- 648 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8kGa1pSjbSYZVebtTRBLxBz5H 649 | 4i2p/llLCrEeQhta5kaQu/RnvuER4W8oDH3+3iuIYW4VQAzyqFpwuzjkDI+17t5t 650 | 0tyazyZ8JXw+KgXTxldMPEL95+qVhgXvwtihXC1c5oGbRlEDvDF6Sa53rcFVsYJ4 651 | ehde/zUxo6UvS7UrBQIDAQAB 652 | -----END PUBLIC KEY----- 653 | )"; 654 | 655 | GIVEN("An encoded payload using a private key") { 656 | auto encoded = jwt::encode(payload, privateKey, "RS384"); 657 | 658 | WHEN("it is decoded using a public key") { 659 | auto decoded = jwt::decode(encoded, publicKey, { "RS384" }); 660 | 661 | THEN("it equals the payload") { 662 | REQUIRE(decoded == payload); 663 | } 664 | } 665 | 666 | WHEN("it is auto decoded with no algorithms specified") { 667 | auto decoded = jwt::decode(encoded, publicKey); 668 | 669 | THEN("the algorithm is determined by the token and properly decoded") { 670 | REQUIRE(decoded == payload); 671 | } 672 | } 673 | 674 | WHEN("it is decoded with the wrong algorithms specified") { 675 | auto decoded = jwt::decode(encoded, publicKey, { "RS512" }); 676 | 677 | THEN("it returns null") { 678 | REQUIRE(decoded == nullptr); 679 | } 680 | } 681 | } 682 | } 683 | 684 | SCENARIO("JWT's can be encoded and decoded using RS512") { 685 | auto payload = R"( 686 | { 687 | "sub": "1234567890", 688 | "name": "John Doe", 689 | "admin": true 690 | } 691 | )"_json; 692 | auto privateKey = R"( 693 | -----BEGIN RSA PRIVATE KEY----- 694 | MIICXAIBAAKBgQC8kGa1pSjbSYZVebtTRBLxBz5H4i2p/llLCrEeQhta5kaQu/Rn 695 | vuER4W8oDH3+3iuIYW4VQAzyqFpwuzjkDI+17t5t0tyazyZ8JXw+KgXTxldMPEL9 696 | 5+qVhgXvwtihXC1c5oGbRlEDvDF6Sa53rcFVsYJ4ehde/zUxo6UvS7UrBQIDAQAB 697 | AoGAb/MXV46XxCFRxNuB8LyAtmLDgi/xRnTAlMHjSACddwkyKem8//8eZtw9fzxz 698 | bWZ/1/doQOuHBGYZU8aDzzj59FZ78dyzNFoF91hbvZKkg+6wGyd/LrGVEB+Xre0J 699 | Nil0GReM2AHDNZUYRv+HYJPIOrB0CRczLQsgFJ8K6aAD6F0CQQDzbpjYdx10qgK1 700 | cP59UHiHjPZYC0loEsk7s+hUmT3QHerAQJMZWC11Qrn2N+ybwwNblDKv+s5qgMQ5 701 | 5tNoQ9IfAkEAxkyffU6ythpg/H0Ixe1I2rd0GbF05biIzO/i77Det3n4YsJVlDck 702 | ZkcvY3SK2iRIL4c9yY6hlIhs+K9wXTtGWwJBAO9Dskl48mO7woPR9uD22jDpNSwe 703 | k90OMepTjzSvlhjbfuPN1IdhqvSJTDychRwn1kIJ7LQZgQ8fVz9OCFZ/6qMCQGOb 704 | qaGwHmUK6xzpUbbacnYrIM6nLSkXgOAwv7XXCojvY614ILTK3iXiLBOxPu5Eu13k 705 | eUz9sHyD6vkgZzjtxXECQAkp4Xerf5TGfQXGXhxIX52yH+N2LtujCdkQZjXAsGdm 706 | B2zNzvrlgRmgBrklMTrMYgm1NPcW+bRLGcwgW2PTvNM= 707 | -----END RSA PRIVATE KEY----- 708 | )"; 709 | auto publicKey = R"( 710 | -----BEGIN PUBLIC KEY----- 711 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8kGa1pSjbSYZVebtTRBLxBz5H 712 | 4i2p/llLCrEeQhta5kaQu/RnvuER4W8oDH3+3iuIYW4VQAzyqFpwuzjkDI+17t5t 713 | 0tyazyZ8JXw+KgXTxldMPEL95+qVhgXvwtihXC1c5oGbRlEDvDF6Sa53rcFVsYJ4 714 | ehde/zUxo6UvS7UrBQIDAQAB 715 | -----END PUBLIC KEY----- 716 | )"; 717 | 718 | GIVEN("An encoded payload using a private key") { 719 | auto encoded = jwt::encode(payload, privateKey, "RS512"); 720 | 721 | WHEN("it is decoded using a public key") { 722 | auto decoded = jwt::decode(encoded, publicKey, { "RS512" }); 723 | 724 | THEN("it equals the payload") { 725 | REQUIRE(decoded == payload); 726 | } 727 | } 728 | 729 | WHEN("it is auto decoded with no algorithms specified") { 730 | auto decoded = jwt::decode(encoded, publicKey); 731 | 732 | THEN("the algorithm is determined by the token and properly decoded") { 733 | REQUIRE(decoded == payload); 734 | } 735 | } 736 | 737 | WHEN("it is decoded with the wrong algorithms specified") { 738 | auto decoded = jwt::decode(encoded, publicKey, { "RS256" }); 739 | 740 | THEN("it returns null") { 741 | REQUIRE(decoded == nullptr); 742 | } 743 | } 744 | } 745 | } 746 | 747 | SCENARIO("JWT's can be encoded and decoded using ES256") { 748 | auto payload = R"( 749 | { 750 | "sub": "1234567890", 751 | "name": "John Doe", 752 | "admin": true 753 | } 754 | )"_json; 755 | auto privateKey = R"( 756 | -----BEGIN EC PRIVATE KEY----- 757 | MIHbAgEBBEGPWb0IqNdCUE270P42PYnRIkqZSaXB9kkWDQkfENA3sTM5Uu+5ZF+B 758 | Wk336PYnNocbvtXUSl3x+1wNyw6Nbp0qpaAHBgUrgQQAI6GBiQOBhgAEAEf2nD9L 759 | RWnmqUSFhaT7AKXEWIhXOTr5s5UXCayDc0oUQR2SrnyevwNvlzarmBE6qZx2MFxS 760 | paPzXtGbPKSn89BMAD+v84XQhyzwA2j0/IISkp+JJyCk3FK4/GqW7ZIhGfu8LZbc 761 | hxGofNuXUwkni7KTi3w0zeEtZSVlFWTdZqCuIdGi 762 | -----END EC PRIVATE KEY----- 763 | )"; 764 | auto publicKey = R"( 765 | -----BEGIN PUBLIC KEY----- 766 | MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAR/acP0tFaeapRIWFpPsApcRYiFc5 767 | OvmzlRcJrINzShRBHZKufJ6/A2+XNquYETqpnHYwXFKlo/Ne0Zs8pKfz0EwAP6/z 768 | hdCHLPADaPT8ghKSn4knIKTcUrj8apbtkiEZ+7wtltyHEah825dTCSeLspOLfDTN 769 | 4S1lJWUVZN1moK4h0aI= 770 | -----END PUBLIC KEY----- 771 | )"; 772 | 773 | GIVEN("An encoded payload using a private key") { 774 | auto encoded = jwt::encode(payload, privateKey, "ES256"); 775 | 776 | WHEN("it is decoded using a public key") { 777 | auto decoded = jwt::decode(encoded, publicKey, { "ES256" }); 778 | 779 | THEN("it equals the payload") { 780 | REQUIRE(decoded == payload); 781 | } 782 | } 783 | 784 | WHEN("it is auto decoded with no algorithms specified") { 785 | auto decoded = jwt::decode(encoded, publicKey); 786 | 787 | THEN("the algorithm is determined by the token and properly decoded") { 788 | REQUIRE(decoded == payload); 789 | } 790 | } 791 | 792 | WHEN("it is decoded with the wrong algorithms specified") { 793 | auto decoded = jwt::decode(encoded, publicKey, { "ES384" }); 794 | 795 | THEN("it returns null") { 796 | REQUIRE(decoded == nullptr); 797 | } 798 | } 799 | } 800 | } 801 | 802 | SCENARIO("JWT's can be encoded and decoded using ES384") { 803 | auto payload = R"( 804 | { 805 | "sub": "1234567890", 806 | "name": "John Doe", 807 | "admin": true 808 | } 809 | )"_json; 810 | auto privateKey = R"( 811 | -----BEGIN EC PRIVATE KEY----- 812 | MIHbAgEBBEGPWb0IqNdCUE270P42PYnRIkqZSaXB9kkWDQkfENA3sTM5Uu+5ZF+B 813 | Wk336PYnNocbvtXUSl3x+1wNyw6Nbp0qpaAHBgUrgQQAI6GBiQOBhgAEAEf2nD9L 814 | RWnmqUSFhaT7AKXEWIhXOTr5s5UXCayDc0oUQR2SrnyevwNvlzarmBE6qZx2MFxS 815 | paPzXtGbPKSn89BMAD+v84XQhyzwA2j0/IISkp+JJyCk3FK4/GqW7ZIhGfu8LZbc 816 | hxGofNuXUwkni7KTi3w0zeEtZSVlFWTdZqCuIdGi 817 | -----END EC PRIVATE KEY----- 818 | )"; 819 | auto publicKey = R"( 820 | -----BEGIN PUBLIC KEY----- 821 | MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAR/acP0tFaeapRIWFpPsApcRYiFc5 822 | OvmzlRcJrINzShRBHZKufJ6/A2+XNquYETqpnHYwXFKlo/Ne0Zs8pKfz0EwAP6/z 823 | hdCHLPADaPT8ghKSn4knIKTcUrj8apbtkiEZ+7wtltyHEah825dTCSeLspOLfDTN 824 | 4S1lJWUVZN1moK4h0aI= 825 | -----END PUBLIC KEY----- 826 | )"; 827 | 828 | GIVEN("An encoded payload using a private key") { 829 | auto encoded = jwt::encode(payload, privateKey, "ES384"); 830 | 831 | WHEN("it is decoded using a public key") { 832 | auto decoded = jwt::decode(encoded, publicKey, { "ES384" }); 833 | 834 | THEN("it equals the payload") { 835 | REQUIRE(decoded == payload); 836 | } 837 | } 838 | 839 | WHEN("it is auto decoded with no algorithms specified") { 840 | auto decoded = jwt::decode(encoded, publicKey); 841 | 842 | THEN("the algorithm is determined by the token and properly decoded") { 843 | REQUIRE(decoded == payload); 844 | } 845 | } 846 | 847 | WHEN("it is decoded with the wrong algorithms specified") { 848 | auto decoded = jwt::decode(encoded, publicKey, { "ES512" }); 849 | 850 | THEN("it returns null") { 851 | REQUIRE(decoded == nullptr); 852 | } 853 | } 854 | } 855 | } 856 | 857 | SCENARIO("JWT's can be encoded and decoded using ES512") { 858 | auto payload = R"( 859 | { 860 | "sub": "1234567890", 861 | "name": "John Doe", 862 | "admin": true 863 | } 864 | )"_json; 865 | auto privateKey = R"( 866 | -----BEGIN EC PRIVATE KEY----- 867 | MIHbAgEBBEGPWb0IqNdCUE270P42PYnRIkqZSaXB9kkWDQkfENA3sTM5Uu+5ZF+B 868 | Wk336PYnNocbvtXUSl3x+1wNyw6Nbp0qpaAHBgUrgQQAI6GBiQOBhgAEAEf2nD9L 869 | RWnmqUSFhaT7AKXEWIhXOTr5s5UXCayDc0oUQR2SrnyevwNvlzarmBE6qZx2MFxS 870 | paPzXtGbPKSn89BMAD+v84XQhyzwA2j0/IISkp+JJyCk3FK4/GqW7ZIhGfu8LZbc 871 | hxGofNuXUwkni7KTi3w0zeEtZSVlFWTdZqCuIdGi 872 | -----END EC PRIVATE KEY----- 873 | )"; 874 | auto publicKey = R"( 875 | -----BEGIN PUBLIC KEY----- 876 | MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAR/acP0tFaeapRIWFpPsApcRYiFc5 877 | OvmzlRcJrINzShRBHZKufJ6/A2+XNquYETqpnHYwXFKlo/Ne0Zs8pKfz0EwAP6/z 878 | hdCHLPADaPT8ghKSn4knIKTcUrj8apbtkiEZ+7wtltyHEah825dTCSeLspOLfDTN 879 | 4S1lJWUVZN1moK4h0aI= 880 | -----END PUBLIC KEY----- 881 | )"; 882 | 883 | GIVEN("An encoded payload using a private key") { 884 | auto encoded = jwt::encode(payload, privateKey, "ES512"); 885 | 886 | WHEN("it is decoded using a public key") { 887 | auto decoded = jwt::decode(encoded, publicKey, { "ES512" }); 888 | 889 | THEN("it equals the payload") { 890 | REQUIRE(decoded == payload); 891 | } 892 | } 893 | 894 | WHEN("it is auto decoded with no algorithms specified") { 895 | auto decoded = jwt::decode(encoded, publicKey); 896 | 897 | THEN("the algorithm is determined by the token and properly decoded") { 898 | REQUIRE(decoded == payload); 899 | } 900 | } 901 | 902 | WHEN("it is decoded with the wrong algorithms specified") { 903 | auto decoded = jwt::decode(encoded, publicKey, { "ES256" }); 904 | 905 | THEN("it returns null") { 906 | REQUIRE(decoded == nullptr); 907 | } 908 | } 909 | } 910 | } --------------------------------------------------------------------------------