Logo Pico-Framework A web-first embedded framework for C++
Loading...
Searching...
No Matches
JwtAuthenticator.cpp
Go to the documentation of this file.
1
19#include "framework_config.h" // Must be included before DebugTrace.h to ensure framework_config.h is processed first
20#include "DebugTrace.h"
22
23#include "http/JwtAuthenticator.h"
24
25#include <iostream>
26#include <chrono>
27#include <mbedtls/md.h>
28#include <mbedtls/base64.h>
29#include <mbedtls/error.h>
30#include <nlohmann/json.hpp>
31
32using json = nlohmann::json;
33
34#ifndef MBEDTLS_SHA256_DIGEST_LENGTH
35#define MBEDTLS_SHA256_DIGEST_LENGTH 32
36#endif
37
43
45std::string JwtAuthenticator::base64urlEncode(const std::string &input) const
46{
47 size_t encoded_len = ((input.length() + 2) / 3) * 4 + 1;
48 char *encoded_data = new char[encoded_len];
49
50 size_t len = 0;
51 int result = mbedtls_base64_encode(reinterpret_cast<unsigned char *>(encoded_data), encoded_len, &len,
52 reinterpret_cast<const unsigned char *>(input.c_str()), input.length());
53
54 if (result != 0)
55 {
56 std::cerr << "Base64 encoding failed with error code: " << result << std::endl;
57 delete[] encoded_data;
58 return "";
59 }
60
61 std::string base64_str(encoded_data, len);
62 std::string base64url_str(base64_str);
63 std::replace(base64url_str.begin(), base64url_str.end(), '+', '-');
64 std::replace(base64url_str.begin(), base64url_str.end(), '/', '_');
65 base64url_str.erase(std::remove(base64url_str.begin(), base64url_str.end(), '='), base64url_str.end());
66
67 delete[] encoded_data;
68 return base64url_str;
69}
70
72bool JwtAuthenticator::base64urlDecode(const std::string &input, std::string &output) const
73{
74 std::string base64_input = input;
75 std::replace(base64_input.begin(), base64_input.end(), '-', '+');
76 std::replace(base64_input.begin(), base64_input.end(), '_', '/');
77 while (base64_input.size() % 4 != 0)
78 {
79 base64_input += "=";
80 }
81
82 for (size_t i = 0; i < base64_input.size(); ++i)
83 {
84 if (static_cast<unsigned char>(base64_input[i]) > 127)
85 {
86 std::cout << "Invalid character found in base64: " << (int)base64_input[i] << std::endl;
87 return false;
88 }
89 }
90
91 size_t decoded_len = base64_input.size() * 3 / 4 + 4;
92 unsigned char *decoded_data = new unsigned char[decoded_len];
93
94 int ret = mbedtls_base64_decode(decoded_data, decoded_len, &decoded_len,
95 (const unsigned char *)base64_input.c_str(), base64_input.size());
96
97 if (ret != 0)
98 {
99 char error_msg[100];
100 mbedtls_strerror(ret, error_msg, sizeof(error_msg));
101 std::cerr << "Base64 decoding failed: " << error_msg << std::endl;
102 delete[] decoded_data;
103 return false;
104 }
105
106 output.assign(reinterpret_cast<char *>(decoded_data), decoded_len);
107 delete[] decoded_data;
108 return true;
109}
110
112bool JwtAuthenticator::isBase64urlEncoded(const std::string &str) const
113{
114 static const std::string base64url_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
115
116 if (str.length() % 4 != 0)
117 {
118 return false;
119 }
120
121 for (char c : str)
122 {
123 if (base64url_chars.find(c) == std::string::npos)
124 {
125 return false;
126 }
127 }
128
129 return true;
130}
131
133std::string JwtAuthenticator::hmacSHA256(const std::string &message) const
134{
135 unsigned char hmac_output[MBEDTLS_SHA256_DIGEST_LENGTH];
136 mbedtls_md_context_t ctx;
137
138 mbedtls_md_init(&ctx);
139 const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
140 mbedtls_md_setup(&ctx, md_info, 1);
141 mbedtls_md_hmac_starts(&ctx, reinterpret_cast<const unsigned char *>(secretKey.c_str()), secretKey.size());
142 mbedtls_md_hmac_update(&ctx, reinterpret_cast<const unsigned char *>(message.c_str()), message.size());
143 mbedtls_md_hmac_finish(&ctx, hmac_output);
144 mbedtls_md_free(&ctx);
145
146 return std::string(reinterpret_cast<char *>(hmac_output), MBEDTLS_SHA256_DIGEST_LENGTH);
147}
148
150std::string JwtAuthenticator::generateJWT(const std::string &userId, const std::string &userName) const
151{
152 json header = {{"alg", "HS256"}, {"typ", "JWT"}};
153 json payload = {
154 {"sub", userId},
155 {"name", userName},
156 {"iat", std::time(0)},
157 {"exp", std::time(0) + 3600}};
158
159 std::string encodedHeader = base64urlEncode(header.dump());
160 std::string encodedPayload = base64urlEncode(payload.dump());
161 std::string message = encodedHeader + "." + encodedPayload;
162 std::string signature = base64urlEncode(hmacSHA256(message));
163
164 return encodedHeader + "." + encodedPayload + "." + signature;
165}
166
168bool JwtAuthenticator::decodeJWT(const std::string &token, std::string &header, std::string &payload, std::string &signature) const
169{
170 size_t first_dot = token.find('.');
171 size_t second_dot = token.find('.', first_dot + 1);
172 if (first_dot == std::string::npos || second_dot == std::string::npos)
173 {
174 return false;
175 }
176
177 header = token.substr(0, first_dot);
178 payload = token.substr(first_dot + 1, second_dot - first_dot - 1);
179 signature = token.substr(second_dot + 1);
180
181 if (isBase64urlEncoded(header))
182 {
183 std::string decoded;
184 if (!base64urlDecode(header, decoded))
185 return false;
186 header = decoded;
187 }
188
189 if (isBase64urlEncoded(payload))
190 {
191 std::string decoded;
192 if (!base64urlDecode(payload, decoded))
193 return false;
194 payload = decoded;
195 }
196
197 return true;
198}
199
201std::string JwtAuthenticator::bytesToBase64url(const unsigned char *data, size_t length) const
202{
203 size_t output_len = length * 4 / 3 + 4;
204 char *base64_output = new char[output_len];
205 size_t written_len = 0;
206
207 mbedtls_base64_encode((unsigned char *)base64_output, output_len, &written_len, data, length);
208
209 std::string base64_result(base64_output, written_len);
210 std::replace(base64_result.begin(), base64_result.end(), '+', '-');
211 std::replace(base64_result.begin(), base64_result.end(), '/', '_');
212 base64_result.erase(std::remove(base64_result.end() - 1, base64_result.end(), '='), base64_result.end());
213
214 delete[] base64_output;
215 return base64_result;
216}
217
219bool JwtAuthenticator::verifyJWTSignature(const std::string &encoded_header, const std::string &encoded_payload, const std::string &signature) const
220{
221 std::string header_payload = encoded_header + "." + encoded_payload;
222
223 mbedtls_md_context_t ctx;
224 mbedtls_md_init(&ctx);
225 const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
226 mbedtls_md_setup(&ctx, md_info, 1);
227 mbedtls_md_hmac_starts(&ctx, (const unsigned char *)secretKey.c_str(), secretKey.length());
228 mbedtls_md_hmac_update(&ctx, (const unsigned char *)header_payload.c_str(), header_payload.length());
229
230 unsigned char hmac_output[MBEDTLS_SHA256_DIGEST_LENGTH];
231 mbedtls_md_hmac_finish(&ctx, hmac_output);
232 mbedtls_md_free(&ctx);
233
234 std::string computed_signature = bytesToBase64url(hmac_output, MBEDTLS_SHA256_DIGEST_LENGTH);
235 return computed_signature == signature;
236}
237
239bool JwtAuthenticator::isJWTPayloadExpired(const std::string &payload) const
240{
241 auto parsed = json::parse(payload, nullptr, false);
242 if (parsed.is_discarded() || !parsed.contains("exp") || !parsed["exp"].is_number_integer())
243 {
244 std::cerr << "Invalid or missing 'exp' in JWT payload." << std::endl;
245 return false;
246 }
247
248 long long exp_timestamp = parsed["exp"].get<long long>();
249 if (exp_timestamp <= 0)
250 return false;
251
252 auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
253 return now >= exp_timestamp;
254}
255
257bool JwtAuthenticator::isJWTExpired(const std::string &token) const
258{
259 size_t first_dot = token.find('.');
260 size_t second_dot = token.find('.', first_dot + 1);
261 if (first_dot == std::string::npos || second_dot == std::string::npos)
262 return false;
263
264 std::string encoded_payload = token.substr(first_dot + 1, second_dot - first_dot - 1);
265 std::string decoded_payload;
266
267 if (!base64urlDecode(encoded_payload, decoded_payload))
268 return false;
269 return isJWTPayloadExpired(decoded_payload);
270}
271
273bool JwtAuthenticator::validateJWT(const std::string &token, bool validateExpiry) const
274{
275 size_t first_dot = token.find('.');
276 size_t second_dot = token.find('.', first_dot + 1);
277 if (first_dot == std::string::npos || second_dot == std::string::npos)
278 return false;
279
280 std::string encoded_header = token.substr(0, first_dot);
281 std::string encoded_payload = token.substr(first_dot + 1, second_dot - first_dot - 1);
282 std::string signature = token.substr(second_dot + 1);
283
284 std::string decoded_header, decoded_payload;
285 if (!base64urlDecode(encoded_header, decoded_header) ||
286 !base64urlDecode(encoded_payload, decoded_payload))
287 {
288 return false;
289 }
290
291 if (validateExpiry && isJWTPayloadExpired(decoded_payload))
292 {
293 return false;
294 }
295
296 return verifyJWTSignature(encoded_header, encoded_payload, signature);
297}
298// convenience initalizer
299void JwtAuthenticator::init(const std::string &secret, int expirySeconds)
300{
301 this->secretKey = secret;
302 this->expiryTime = expirySeconds;
303}
Macro-based debug trace system with optional SD file logging.
#define TRACE_INIT(MODULE_NAME)
Declare trace usage in a source file for a given module.
Definition DebugTrace.h:170
nlohmann::json json
#define MBEDTLS_SHA256_DIGEST_LENGTH
JWT JwtAuthenticator for embedded applications.
bool validateJWT(const std::string &token, bool validateExpiry=false) const
Validate a JWT's signature and optionally its expiration.
std::string base64urlEncode(const std::string &input) const
Encode a string using base64url encoding.
std::string hmacSHA256(const std::string &message) const
Calculate HMAC-SHA256 for a given message.
bool isJWTPayloadExpired(const std::string &payload) const
Check if a decoded JWT payload is expired.
bool verifyJWTSignature(const std::string &encoded_header, const std::string &encoded_payload, const std::string &signature) const
Verify a JWT's signature using HMAC-SHA256.
std::string bytesToBase64url(const unsigned char *data, size_t length) const
Convert a byte buffer to a base64url string.
std::string generateJWT(const std::string &userId, const std::string &userName) const
Generate a JWT for a given user.
bool isJWTExpired(const std::string &token) const
Check if a JWT is expired based on the exp claim.
bool decodeJWT(const std::string &token, std::string &header, std::string &payload, std::string &signature) const
Decode a JWT into its components.
std::string expiryTime
void init(const std::string &secret, int expirySeconds)
Initialize the JwtAuthenticator with a secret key and expiry time.
bool base64urlDecode(const std::string &input, std::string &output) const
Decode a base64url-encoded string.
JwtAuthenticator()
Get an instance of the JwtAuthenticator.
bool isBase64urlEncoded(const std::string &str) const
Check if a string is valid base64url.
std::string secretKey
Construct a new JwtAuthenticator object.
Delegates to user or system configuration.