cxxmcp 1.1.6
C++ MCP SDK
Loading...
Searching...
No Matches
jwks.hpp
Go to the documentation of this file.
1// Copyright (c) 2025 [caomengxuan666]
2
3#pragma once
4
5#include <nlohmann/json.hpp>
6#include <optional>
7#include <string>
8#include <utility>
9#include <vector>
10
13
16
17namespace mcp::auth {
18
24struct JsonWebKey {
25 std::string key_type;
26 std::optional<std::string> public_key_use;
27 StringList key_operations;
28 std::optional<std::string> algorithm;
29 std::optional<std::string> key_id;
30 std::optional<std::string> curve;
31 std::optional<std::string> x;
32 std::optional<std::string> y;
33 std::optional<std::string> modulus;
34 std::optional<std::string> exponent;
35 StringList certificate_chain;
36 MetadataMap metadata;
37};
38
41 std::vector<JsonWebKey> keys;
42 MetadataMap metadata;
43};
44
47 std::optional<std::string> key_id;
48 std::optional<std::string> algorithm;
49 std::optional<std::string> key_type;
50 std::optional<std::string> public_key_use;
51};
52
55 std::string jwks_uri;
56 HeaderMap headers;
57};
58
61 public:
62 virtual ~JwksEndpoint() = default;
63
64 virtual core::Result<JsonWebKeySet> fetch_jwks(
65 const JwksFetchRequest& request) = 0;
66};
67
69class JwksCache {
70 public:
71 virtual ~JwksCache() = default;
72
74 const std::string& jwks_uri) = 0;
75 virtual core::Result<core::Unit> save(std::string jwks_uri,
76 JsonWebKeySet keys) = 0;
77 virtual core::Result<core::Unit> clear(const std::string& jwks_uri) = 0;
78};
79
81class InMemoryJwksCache final : public JwksCache {
82 public:
84 const std::string& jwks_uri) override {
85 for (const auto& entry : entries_) {
86 if (constant_time_string_equal(entry.first, jwks_uri)) {
87 return entry.second;
88 }
89 }
90 return std::optional<JsonWebKeySet>{};
91 }
92
93 core::Result<core::Unit> save(std::string jwks_uri,
94 JsonWebKeySet keys) override {
95 for (auto& entry : entries_) {
96 if (constant_time_string_equal(entry.first, jwks_uri)) {
97 entry.second = std::move(keys);
98 return core::Unit{};
99 }
100 }
101 entries_.emplace_back(std::move(jwks_uri), std::move(keys));
102 return core::Unit{};
103 }
104
105 core::Result<core::Unit> clear(const std::string& jwks_uri) override {
106 for (auto iter = entries_.begin(); iter != entries_.end(); ++iter) {
107 if (constant_time_string_equal(iter->first, jwks_uri)) {
108 entries_.erase(iter);
109 break;
110 }
111 }
112 return core::Unit{};
113 }
114
115 private:
116 std::vector<std::pair<std::string, JsonWebKeySet>> entries_;
117};
118
119namespace jwks_detail {
120
121using Json = nlohmann::json;
122
123inline std::optional<std::string> optional_string(const Json& value,
124 const char* key) {
125 const auto iter = value.find(key);
126 if (iter == value.end() || iter->is_null()) {
127 return std::nullopt;
128 }
129 if (iter->is_string()) {
130 return iter->get<std::string>();
131 }
132 return std::nullopt;
133}
134
135inline std::string string_or_empty(const Json& value, const char* key) {
136 auto result = optional_string(value, key);
137 return result.has_value() ? *result : std::string{};
138}
139
140inline StringList string_list_or_empty(const Json& value, const char* key) {
141 StringList result;
142 const auto iter = value.find(key);
143 if (iter == value.end() || !iter->is_array()) {
144 return result;
145 }
146 for (const auto& entry : *iter) {
147 if (entry.is_string()) {
148 result.push_back(entry.get<std::string>());
149 }
150 }
151 return result;
152}
153
154inline MetadataMap extension_metadata(const Json& value) {
155 MetadataMap result;
156 for (auto iter = value.begin(); iter != value.end(); ++iter) {
157 const auto key = iter.key();
158 if (key == "keys" || key == "kty" || key == "use" || key == "key_ops" ||
159 key == "alg" || key == "kid" || key == "crv" || key == "x" ||
160 key == "y" || key == "n" || key == "e" || key == "x5c") {
161 continue;
162 }
163 if (iter->is_string()) {
164 result.emplace(key, iter->get<std::string>());
165 } else if (iter->is_boolean()) {
166 result.emplace(key, iter->get<bool>() ? "true" : "false");
167 } else if (iter->is_number() || iter->is_object() || iter->is_array()) {
168 result.emplace(key, iter->dump());
169 }
170 }
171 return result;
172}
173
174inline bool optional_matches(const std::optional<std::string>& expected,
175 const std::optional<std::string>& actual) {
176 return !expected.has_value() || (actual.has_value() && *actual == *expected);
177}
178
179} // namespace jwks_detail
180
183 const nlohmann::json& value) {
184 if (!value.is_object()) {
185 return mcp::core::unexpected(
186 make_oauth_error(OAuthErrorCode::kMetadataDiscoveryFailed,
187 "JWK JSON value must be an object"));
188 }
189
190 JsonWebKey key;
191 key.key_type = jwks_detail::string_or_empty(value, "kty");
192 if (key.key_type.empty()) {
193 return mcp::core::unexpected(make_oauth_error(
194 OAuthErrorCode::kMetadataDiscoveryFailed, "JWK kty is required"));
195 }
196 key.public_key_use = jwks_detail::optional_string(value, "use");
197 key.key_operations = jwks_detail::string_list_or_empty(value, "key_ops");
198 key.algorithm = jwks_detail::optional_string(value, "alg");
199 key.key_id = jwks_detail::optional_string(value, "kid");
200 key.curve = jwks_detail::optional_string(value, "crv");
201 key.x = jwks_detail::optional_string(value, "x");
202 key.y = jwks_detail::optional_string(value, "y");
203 key.modulus = jwks_detail::optional_string(value, "n");
204 key.exponent = jwks_detail::optional_string(value, "e");
205 key.certificate_chain = jwks_detail::string_list_or_empty(value, "x5c");
206 key.metadata = jwks_detail::extension_metadata(value);
207 return key;
208}
209
212 const nlohmann::json& value) {
213 if (!value.is_object()) {
214 return mcp::core::unexpected(
215 make_oauth_error(OAuthErrorCode::kMetadataDiscoveryFailed,
216 "JWKS JSON value must be an object"));
217 }
218 const auto keys = value.find("keys");
219 if (keys == value.end() || !keys->is_array()) {
220 return mcp::core::unexpected(
221 make_oauth_error(OAuthErrorCode::kMetadataDiscoveryFailed,
222 "JWKS keys array is required"));
223 }
224
225 JsonWebKeySet set;
226 for (const auto& entry : *keys) {
227 auto parsed = parse_json_web_key(entry);
228 if (!parsed.has_value()) {
229 return mcp::core::unexpected(parsed.error());
230 }
231 set.keys.push_back(std::move(*parsed));
232 }
233 set.metadata = jwks_detail::extension_metadata(value);
234 return set;
235}
236
242 const JsonWebKeySet& set, const JwkSelectionCriteria& criteria) {
243 std::optional<JsonWebKey> selected;
244 for (const auto& key : set.keys) {
245 if (!jwks_detail::optional_matches(criteria.key_id, key.key_id) ||
246 !jwks_detail::optional_matches(criteria.algorithm, key.algorithm) ||
247 !jwks_detail::optional_matches(
248 criteria.key_type, std::optional<std::string>{key.key_type}) ||
249 !jwks_detail::optional_matches(criteria.public_key_use,
250 key.public_key_use)) {
251 continue;
252 }
253 if (selected.has_value()) {
254 return mcp::core::unexpected(
255 make_oauth_error(OAuthErrorCode::kMetadataDiscoveryFailed,
256 "JWKS key selection is ambiguous"));
257 }
258 selected = key;
259 }
260 if (!selected.has_value()) {
261 return mcp::core::unexpected(
262 make_oauth_error(OAuthErrorCode::kMetadataDiscoveryFailed,
263 "JWKS key selection found no matching key"));
264 }
265 return *selected;
266}
267
268} // namespace mcp::auth
In-process JWKS cache for tests and embedded clients.
Definition jwks.hpp:81
Application-provided JWKS cache boundary.
Definition jwks.hpp:69
Application-provided JWKS retrieval boundary.
Definition jwks.hpp:60
bool constant_time_string_equal(std::string_view lhs, std::string_view rhs) noexcept
Compare two strings without data-dependent early exit.
Definition constant_time.hpp:17
core::Result< JsonWebKeySet > parse_json_web_key_set(const nlohmann::json &value)
Parse a public JWKS document from JSON.
Definition jwks.hpp:211
core::Result< JsonWebKey > parse_json_web_key(const nlohmann::json &value)
Parse a public JWK from JSON without performing trust decisions.
Definition jwks.hpp:182
core::Result< JsonWebKey > select_json_web_key(const JsonWebKeySet &set, const JwkSelectionCriteria &criteria)
Select a single JWK by stable JWT header criteria.
Definition jwks.hpp:241
OAuth authorization lifecycle contracts and lightweight state logic.
core::Error make_oauth_error(OAuthErrorCode code, std::string message, std::string detail={})
Build an auth-category lifecycle error.
Definition lifecycle.hpp:50
Shared result and error primitives used by the public cxxmcp SDK.
std::monostate Unit
Success value for operations that only need to report failure.
Definition result.hpp:55
tl::expected< T, Error > Result
Alias for the SDK result type.
Definition result.hpp:64
JSON Web Key Set value model.
Definition jwks.hpp:40
Public JSON Web Key value model.
Definition jwks.hpp:24
Criteria for selecting a public JWK before verification.
Definition jwks.hpp:46
Request for retrieving a JWKS document.
Definition jwks.hpp:54