12#include <unordered_map>
26inline core::Error dpop_error(std::string message, std::string detail = {}) {
27 return core::Error{1, std::move(message), std::move(detail),
28 std::string(AuthErrorCategory)};
31inline std::string uppercase_ascii(std::string_view value) {
32 std::string output(value);
34 output.begin(), output.end(), output.begin(),
35 [](
unsigned char ch) { return static_cast<char>(std::toupper(ch)); });
51 : value_(value ==
nullptr ?
"" : value) {}
52 explicit SecureString(std::string value) : value_(std::move(value)) {}
53 explicit SecureString(std::string_view value) : value_(value) {}
59 : value_(std::move(other.value_)) {
68 value_ = std::move(other.value_);
74 reset(std::move(value));
80 void reset(std::string value = {}) {
82 value_ = std::move(value);
85 std::string_view view()
const noexcept {
return value_; }
86 const std::string& str()
const noexcept {
return value_; }
87 bool empty()
const noexcept {
return value_.empty(); }
88 std::size_t size()
const noexcept {
return value_.size(); }
91 void zeroize()
noexcept {
95 volatile char* data = value_.empty() ? nullptr : &value_[0];
96 for (std::size_t index = 0; index < value_.size(); ++index) {
111 std::string algorithm;
119 std::optional<std::string> access_token;
120 std::optional<std::string> nonce;
129 std::optional<std::string> access_token_hash;
130 std::optional<std::string> nonce;
143 TimePoint expires_at,
154 TimePoint now)
override {
155 if (jwt_id.empty()) {
156 return mcp::core::unexpected(
157 detail::dpop_error(
"DPoP proof jti is required"));
160 std::lock_guard<std::mutex> lock(mutex_);
161 for (
auto iter = seen_.begin(); iter != seen_.end();) {
162 if (iter->second <= now) {
163 iter = seen_.erase(iter);
169 const auto existing = seen_.find(jwt_id);
170 if (existing != seen_.end() && existing->second > now) {
173 seen_[std::move(jwt_id)] = expires_at;
179 std::unordered_map<std::string, TimePoint> seen_;
184 TimePoint now = SystemClock::now();
185 std::chrono::seconds clock_skew_tolerance{300};
186 std::chrono::seconds replay_ttl{300};
187 bool case_sensitive_method =
true;
188 std::optional<std::string> expected_access_token_hash;
198 const std::optional<std::string>& access_token,
200 DpopReplayCache* replay_cache =
nullptr) {
201 if (claims.jwt_id.empty()) {
202 return mcp::core::unexpected(
203 detail::dpop_error(
"DPoP proof jti is required"));
205 if (target.method.empty()) {
206 return mcp::core::unexpected(
207 detail::dpop_error(
"HTTP request method is required"));
209 if (target.url.empty()) {
211 detail::dpop_error(
"HTTP request URL is required"));
213 if (claims.method.empty()) {
215 detail::dpop_error(
"DPoP proof htm is required"));
217 if (claims.url.empty()) {
219 detail::dpop_error(
"DPoP proof htu is required"));
222 const bool method_matches = options.case_sensitive_method
223 ? claims.method == target.method
224 : detail::uppercase_ascii(claims.method) ==
225 detail::uppercase_ascii(target.method);
226 if (!method_matches) {
228 "DPoP proof htm does not match request method", claims.method));
230 if (claims.url != target.url) {
232 "DPoP proof htu does not match request URL", claims.url));
235 if (claims.issued_at > options.now + options.clock_skew_tolerance) {
237 detail::dpop_error(
"DPoP proof iat is too far in the future"));
239 if (claims.issued_at + options.clock_skew_tolerance < options.now) {
241 detail::dpop_error(
"DPoP proof iat is too old"));
244 if (access_token.has_value()) {
245 if (!claims.access_token_hash.has_value() ||
246 claims.access_token_hash->empty()) {
248 detail::dpop_error(
"DPoP proof ath is required"));
250 if (!options.expected_access_token_hash.has_value() ||
251 options.expected_access_token_hash->empty()) {
253 detail::dpop_error(
"expected DPoP access-token hash is required"));
255 if (*claims.access_token_hash != *options.expected_access_token_hash) {
257 detail::dpop_error(
"DPoP proof ath does not match access token"));
261 if (replay_cache !=
nullptr) {
262 const auto remembered = replay_cache->remember_once(
263 claims.jwt_id, options.now + options.replay_ttl, options.now);
264 if (!remembered.has_value()) {
269 detail::dpop_error(
"DPoP proof replay detected"));
292 std::optional<std::string> issuer;
293 std::optional<std::string> audience;
294 std::optional<std::string> required_algorithm;
295 MetadataMap required_claims;
296 TimePoint now = SystemClock::now();
303 std::string audience;
304 std::optional<TimePoint> issued_at;
305 std::optional<TimePoint> expires_at;
321 std::string access_token;
322 std::optional<std::string> nonce;
323 std::string authorization_scheme =
"DPoP";
339 if (request.target.method.empty()) {
340 return mcp::core::unexpected(
341 detail::dpop_error(
"HTTP request method is required"));
343 if (request.target.url.empty()) {
344 return mcp::core::unexpected(
345 detail::dpop_error(
"HTTP request URL is required"));
348 auto proof = signer.sign(request);
349 if (!proof.has_value()) {
350 return mcp::core::unexpected(proof.error());
352 if (proof->empty()) {
353 return mcp::core::unexpected(
354 detail::dpop_error(
"DPoP signer returned an empty proof"));
358 result.proof = std::move(*proof);
359 result.headers.emplace(
"DPoP", result.proof);
366 if (request.access_token.empty()) {
367 return mcp::core::unexpected(
368 detail::dpop_error(
"DPoP access token is required"));
370 if (request.authorization_scheme.empty()) {
371 return mcp::core::unexpected(
372 detail::dpop_error(
"DPoP authorization scheme is required"));
376 proof_request.target = std::move(request.target);
377 proof_request.key = std::move(request.key);
378 proof_request.access_token = request.access_token;
379 proof_request.nonce = std::move(request.nonce);
382 if (!headers.has_value()) {
383 return mcp::core::unexpected(headers.error());
385 headers->headers.emplace(
"Authorization", request.authorization_scheme +
" " +
386 request.access_token);
397 const std::optional<std::string>& access_token) = 0;
Shared lightweight value types for cxxmcp auth contracts.
Replay cache boundary used by DPoP proof validators.
Definition dpop.hpp:138
DPoP proof construction boundary.
Definition dpop.hpp:310
DPoP proof verification boundary for server-side auth providers.
Definition dpop.hpp:391
Thread-safe in-memory replay cache for process-local DPoP validation.
Definition dpop.hpp:151
JWT verification boundary for access tokens and client assertions.
Definition dpop.hpp:401
Small owning string wrapper that zeroizes stored bytes on reset and destruction.
Definition dpop.hpp:47
core::Result< DpopAuthorizationHeaders > build_dpop_proof_headers(DpopSigner &signer, DpopProofRequest request)
Build only the DPoP proof header for an HTTP request.
Definition dpop.hpp:337
core::Result< core::Unit > validate_dpop_proof_claims(const DpopProofClaims &claims, const HttpRequestTarget &target, const std::optional< std::string > &access_token, const DpopClaimValidationOptions &options={}, DpopReplayCache *replay_cache=nullptr)
Validate DPoP claims after JWT signature verification.
Definition dpop.hpp:196
core::Result< DpopAuthorizationHeaders > build_dpop_authorization_headers(DpopSigner &signer, DpopAuthorizationRequest request)
Build Authorization and DPoP headers for a resource request.
Definition dpop.hpp:364
JwtVerificationPurpose
JWT verification purpose for OAuth/DPoP deployments.
Definition dpop.hpp:277
Shared result and error primitives used by the public cxxmcp SDK.
tl::expected< T, Error > Result
Alias for the SDK result type.
Definition result.hpp:64
constexpr auto unexpected(E &&value)
Creates an unexpected result value for the active expected backend.
Definition result.hpp:24
Input for authorizing an HTTP resource request with DPoP.
Definition dpop.hpp:318
Options for validating verified DPoP claims against an HTTP request.
Definition dpop.hpp:183
Private key handle for DPoP proof generation.
Definition dpop.hpp:109
Parsed or verified DPoP proof claims.
Definition dpop.hpp:124
Input for constructing a DPoP proof JWT.
Definition dpop.hpp:116
Transport-neutral HTTP request descriptor used by auth helpers.
Definition types.hpp:29
Input for signature- and claims-verified JWT validation.
Definition dpop.hpp:289
Claims returned only after JWT signature and claim validation.
Definition dpop.hpp:300
OAuth token models and storage contracts.