42 TimePoint now = SystemClock::now())
43 : post_(std::move(post)), now_(now) {}
47 if (request.authorization_server.token_endpoint.empty()) {
49 OAuthErrorCode::kTokenExchangeFailed,
"token endpoint is required"));
52 auto form = FormBuilder{};
53 form.add(
"grant_type",
"authorization_code");
54 form.add(
"code", request.authorization_code);
55 form.add(
"redirect_uri", request.client.redirect_uri);
56 form.add(
"client_id", request.client.client_id);
58 apply_client_auth(&form, &headers, request.client);
59 form.add(
"code_verifier", request.state.pkce.code_verifier);
60 if (!request.resource.empty()) {
61 form.add(
"resource", request.resource);
63 append_additional_parameters(&form, request.additional_parameters);
65 auto response = post_form(request.authorization_server.token_endpoint,
66 std::move(form).body(), std::move(headers));
67 if (!response.has_value()) {
68 return mcp::core::unexpected(response.error());
70 return parse_token_set(*response, OAuthErrorCode::kTokenExchangeFailed);
75 if (request.authorization_server.token_endpoint.empty()) {
77 OAuthErrorCode::kTokenRefreshFailed,
"token endpoint is required"));
80 auto form = FormBuilder{};
81 form.add(
"grant_type",
"refresh_token");
82 form.add(
"refresh_token", request.refresh_token);
83 form.add(
"client_id", request.client.client_id);
85 apply_client_auth(&form, &headers, request.client);
86 if (!request.resource.empty()) {
87 form.add(
"resource", request.resource);
89 const auto scope = detail::join_scopes(request.scopes);
91 form.add(
"scope", scope);
93 append_additional_parameters(&form, request.additional_parameters);
95 auto response = post_form(request.authorization_server.token_endpoint,
96 std::move(form).body(), std::move(headers));
97 if (!response.has_value()) {
98 return mcp::core::unexpected(response.error());
101 parse_token_set(*response, OAuthErrorCode::kTokenRefreshFailed);
102 if (!token_set.has_value()) {
103 return mcp::core::unexpected(token_set.error());
106 result.refresh_token_rotated = token_set->refresh_token.has_value();
107 result.token_set = std::move(*token_set);
113 if (request.authorization_server.token_endpoint.empty()) {
114 return mcp::core::unexpected(
116 "token endpoint is required"));
119 auto form = FormBuilder{};
120 form.add(
"grant_type",
"client_credentials");
121 form.add(
"client_id", request.credentials.client_id);
123 client.client_id = request.credentials.client_id;
124 client.client_secret = request.credentials.client_secret;
125 client.metadata = request.credentials.metadata;
127 apply_client_auth(&form, &headers, client);
128 form.add(
"resource", request.credentials.resource);
129 const auto scope = detail::join_scopes(request.credentials.scopes);
130 if (!scope.empty()) {
131 form.add(
"scope", scope);
133 append_additional_parameters(&form, request.additional_parameters);
135 auto response = post_form(request.authorization_server.token_endpoint,
136 std::move(form).body(), std::move(headers));
137 if (!response.has_value()) {
138 return mcp::core::unexpected(response.error());
140 return parse_token_set(*response, OAuthErrorCode::kClientCredentialsFailed);
146 OAuthErrorCode error_code = OAuthErrorCode::kTokenExchangeFailed) {
147 if (authorization_server.token_endpoint.empty()) {
148 return mcp::core::unexpected(
151 if (parameters.find(
"grant_type") == parameters.end()) {
152 return mcp::core::unexpected(
156 auto form = FormBuilder{};
157 if (!client.client_id.empty()) {
158 form.add(
"client_id", client.client_id);
161 apply_client_auth(&form, &headers, client);
162 append_additional_parameters(&form, parameters);
164 auto response = post_form(authorization_server.token_endpoint,
165 std::move(form).body(), std::move(headers));
166 if (!response.has_value()) {
167 return mcp::core::unexpected(response.error());
169 return parse_token_set(*response, error_code);
173 using Json = nlohmann::json;
177 void add(
const std::string& name,
const std::string& value) {
178 if (!body_.empty()) {
179 body_.push_back(
'&');
181 body_.append(detail::oauth_url_encode(name));
182 body_.push_back(
'=');
183 body_.append(detail::oauth_url_encode(value));
186 std::string body() && {
return std::move(body_); }
192 static void append_additional_parameters(FormBuilder* form,
193 const MetadataMap& parameters) {
194 for (
const auto& parameter : parameters) {
195 form->add(parameter.first, parameter.second);
200 const auto iter = client.metadata.find(
"token_endpoint_auth_method");
201 if (iter != client.metadata.end() && !iter->second.empty()) {
204 return client.client_secret.has_value() ?
"client_secret_post" :
"none";
207 static std::string base64_encode(std::string_view input) {
208 static constexpr char alphabet[] =
209 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
211 output.reserve(((input.size() + 2) / 3) * 4);
212 std::size_t index = 0;
213 while (index + 3 <= input.size()) {
214 const auto b0 =
static_cast<unsigned char>(input[index++]);
215 const auto b1 =
static_cast<unsigned char>(input[index++]);
216 const auto b2 =
static_cast<unsigned char>(input[index++]);
217 output.push_back(alphabet[b0 >> 2]);
218 output.push_back(alphabet[((b0 & 0x03) << 4) | (b1 >> 4)]);
219 output.push_back(alphabet[((b1 & 0x0F) << 2) | (b2 >> 6)]);
220 output.push_back(alphabet[b2 & 0x3F]);
222 const auto remaining = input.size() - index;
223 if (remaining == 1) {
224 const auto b0 =
static_cast<unsigned char>(input[index]);
225 output.push_back(alphabet[b0 >> 2]);
226 output.push_back(alphabet[(b0 & 0x03) << 4]);
227 output.push_back(
'=');
228 output.push_back(
'=');
229 }
else if (remaining == 2) {
230 const auto b0 =
static_cast<unsigned char>(input[index]);
231 const auto b1 =
static_cast<unsigned char>(input[index + 1]);
232 output.push_back(alphabet[b0 >> 2]);
233 output.push_back(alphabet[((b0 & 0x03) << 4) | (b1 >> 4)]);
234 output.push_back(alphabet[(b1 & 0x0F) << 2]);
235 output.push_back(
'=');
240 static void apply_client_auth(FormBuilder* form, HeaderMap* headers,
242 if (form ==
nullptr || headers ==
nullptr || !client.client_secret) {
245 const auto method = client_auth_method(client);
246 if (method ==
"client_secret_basic") {
247 const auto credentials = detail::oauth_url_encode(client.client_id) +
249 detail::oauth_url_encode(*client.client_secret);
250 headers->emplace(
"Authorization",
"Basic " + base64_encode(credentials));
253 if (method ==
"client_secret_post") {
254 form->add(
"client_secret", *client.client_secret);
259 HeaderMap headers = {})
const {
261 return mcp::core::unexpected(
263 "OAuth token HTTP POST endpoint is not configured"));
267 request.url = std::move(url);
268 request.headers.emplace(
"Accept",
"application/json");
269 request.headers.emplace(
"Content-Type",
270 "application/x-www-form-urlencoded");
271 for (
auto& header : headers) {
272 request.headers[std::move(header.first)] = std::move(header.second);
274 request.body = std::move(body);
276 auto response = post_(request);
277 if (!response.has_value()) {
278 return mcp::core::unexpected(response.error());
285 if (response.status_code < 200 || response.status_code >= 300) {
287 error_code,
"OAuth token endpoint returned non-success HTTP status",
288 std::to_string(response.status_code)));
293 value = Json::parse(response.body);
294 }
catch (
const Json::exception& error) {
296 error_code,
"OAuth token endpoint returned invalid JSON",
299 if (!value.is_object()) {
301 error_code,
"OAuth token endpoint response must be an object"));
304 const auto access_token = optional_string(value,
"access_token");
305 if (!access_token.has_value() || access_token->empty()) {
307 error_code,
"OAuth token endpoint response requires access_token"));
311 token_set.access_token = *access_token;
312 token_set.token_type =
313 optional_string(value,
"token_type").value_or(
"Bearer");
314 token_set.refresh_token = optional_string(value,
"refresh_token");
315 if (
auto scope = optional_string(value,
"scope"); scope.has_value()) {
316 token_set.scopes = detail::split_scopes(*scope);
318 if (
auto expires_in = optional_integer(value,
"expires_in");
319 expires_in.has_value() && *expires_in > 0) {
320 token_set.expires_at = now_ + std::chrono::seconds(*expires_in);
322 token_set.metadata = extension_metadata(value);
326 static std::optional<std::string> optional_string(
const Json& value,
328 const auto iter = value.find(key);
329 if (iter == value.end() || iter->is_null()) {
332 if (iter->is_string()) {
333 return iter->get<std::string>();
338 static std::optional<std::int64_t> optional_integer(
const Json& value,
340 const auto iter = value.find(key);
341 if (iter == value.end() || iter->is_null()) {
344 if (iter->is_number_integer() || iter->is_number_unsigned()) {
345 return iter->get<std::int64_t>();
350 static MetadataMap extension_metadata(
const Json& value) {
352 for (
auto iter = value.begin(); iter != value.end(); ++iter) {
353 const auto& key = iter.key();
354 if (key ==
"access_token" || key ==
"token_type" ||
355 key ==
"refresh_token" || key ==
"expires_in" || key ==
"scope") {
358 if (iter->is_string()) {
359 result.emplace(key, iter->get<std::string>());
360 }
else if (iter->is_boolean()) {
361 result.emplace(key, iter->get<
bool>() ?
"true" :
"false");
362 }
else if (iter->is_number() || iter->is_object() || iter->is_array()) {
363 result.emplace(key, iter->dump());