871 if (request.authorization_server.authorization_endpoint.empty()) {
873 OAuthErrorCode::kInvalidRequest,
"authorization endpoint is required"));
875 if (request.client.client_id.empty()) {
877 OAuthErrorCode::kInvalidRequest,
"client_id is required"));
879 if (request.client.redirect_uri.empty()) {
881 OAuthErrorCode::kInvalidRequest,
"redirect_uri is required"));
883 if (!detail::url_uses_https(
884 request.authorization_server.authorization_endpoint) &&
885 !detail::url_uses_loopback_http(
886 request.authorization_server.authorization_endpoint)) {
888 OAuthErrorCode::kInvalidRequest,
889 "authorization endpoint must use HTTPS or loopback HTTP"));
891 if (!request.authorization_server.token_endpoint.empty() &&
892 !detail::url_uses_https(request.authorization_server.token_endpoint) &&
893 !detail::url_uses_loopback_http(
894 request.authorization_server.token_endpoint)) {
895 return mcp::core::unexpected(
897 "token endpoint must use HTTPS or loopback HTTP"));
899 if (!detail::redirect_uri_is_secure(request.client.redirect_uri)) {
900 return mcp::core::unexpected(
902 "redirect_uri must use HTTPS or loopback HTTP"));
904 if (request.state.empty()) {
905 return mcp::core::unexpected(
908 if (request.pkce.code_challenge.empty() ||
909 request.pkce.code_verifier.empty()) {
910 return mcp::core::unexpected(
912 "PKCE verifier and challenge are required"));
914 auto url = request.authorization_server.authorization_endpoint;
915 detail::append_query_param(&url,
"response_type",
"code");
916 detail::append_query_param(&url,
"client_id", request.client.client_id);
917 detail::append_query_param(&url,
"redirect_uri", request.client.redirect_uri);
918 detail::append_query_param(&url,
"state", request.state);
919 detail::append_query_param(&url,
"code_challenge",
920 request.pkce.code_challenge);
921 detail::append_query_param(&url,
"code_challenge_method",
922 detail::pkce_method_name(request.pkce.method));
923 if (!request.resource.empty()) {
924 detail::append_query_param(&url,
"resource", request.resource);
926 const auto scope = detail::join_scopes(request.scopes);
927 if (!scope.empty()) {
928 detail::append_query_param(&url,
"scope", scope);
930 for (
const auto& parameter : request.additional_parameters) {
931 detail::append_query_param(&url, parameter.first, parameter.second);
935 stored.state = request.state;
936 stored.pkce = request.pkce;
937 stored.resource = request.resource;
938 stored.client_id = request.client.client_id;
939 stored.redirect_uri = request.client.redirect_uri;
940 stored.requested_scopes = request.scopes;
1005 : resource_(std::move(resource)),
1006 metadata_(std::move(metadata)),
1007 client_(std::move(client)) {}
1009 void set_resource(std::string resource) { resource_ = std::move(resource); }
1011 metadata_ = std::move(metadata);
1014 client_ = std::move(client);
1016 void set_credential_store(std::shared_ptr<CredentialStore> store) {
1017 credential_store_ = std::move(store);
1019 void set_state_store(std::shared_ptr<StateStore> store) {
1020 state_store_ = std::move(store);
1022 void set_token_endpoint(std::shared_ptr<OAuthTokenEndpoint> endpoint) {
1023 token_endpoint_ = std::move(endpoint);
1025 void set_client_registration_endpoint(
1026 std::shared_ptr<OAuthClientRegistrationEndpoint> endpoint) {
1027 registration_endpoint_ = std::move(endpoint);
1030 scope_upgrade_config_ = config;
1032 void set_authorization_state_ttl(std::chrono::seconds ttl) {
1033 authorization_state_ttl_ = ttl;
1038 const ScopeList& current_scopes()
const {
return current_scopes_; }
1039 std::chrono::seconds authorization_state_ttl()
const {
1040 return authorization_state_ttl_;
1042 std::uint32_t scope_upgrade_attempts()
const {
1043 return scope_upgrade_attempts_;
1047 return CredentialKey{resource_, metadata_.issuer, client_.client_id, {}};
1051 std::string client_id, std::string redirect_uri = {},
1052 ScopeList scopes = {}) {
1053 if (client_id.empty()) {
1055 OAuthErrorCode::kInvalidRequest,
"client_id is required"));
1057 if (redirect_uri.empty()) {
1058 redirect_uri = client_.redirect_uri;
1060 if (redirect_uri.empty()) {
1062 OAuthErrorCode::kInvalidRequest,
"redirect_uri is required"));
1065 config.client_id = std::move(client_id);
1066 config.client_secret = client_.client_secret;
1067 config.redirect_uri = std::move(redirect_uri);
1068 config.scopes = std::move(scopes);
1069 configure_client(config);
1074 std::string client_id_metadata_url, std::string redirect_uri,
1075 ScopeList scopes = {}) {
1078 OAuthErrorCode::kClientMetadataDocumentUnsupported,
1079 "authorization server does not advertise Client ID Metadata "
1080 "Document support"));
1084 OAuthErrorCode::kClientMetadataDocumentInvalid,
1085 "client_id metadata document URL must be HTTPS with a non-root path",
1086 client_id_metadata_url));
1088 return configure_client_id(std::move(client_id_metadata_url),
1089 std::move(redirect_uri), std::move(scopes));
1094 if (!registration_endpoint_) {
1096 OAuthErrorCode::kClientRegistrationUnavailable,
1097 "dynamic client registration endpoint is not configured"));
1099 if (!metadata_.registration_endpoint.has_value() ||
1100 metadata_.registration_endpoint->empty()) {
1102 OAuthErrorCode::kClientRegistrationUnavailable,
1103 "authorization server metadata has no registration endpoint"));
1107 if (!registration.has_value()) {
1108 return mcp::core::unexpected(registration.error());
1112 request.registration_endpoint = *metadata_.registration_endpoint;
1113 request.headers = std::move(headers);
1114 request.registration = std::move(*registration);
1116 auto response = registration_endpoint_->register_client(request);
1117 if (!response.has_value()) {
1119 OAuthErrorCode::kClientRegistrationFailed,
1120 "dynamic client registration failed", response.error().message));
1122 if (response->client_id.empty()) {
1124 OAuthErrorCode::kClientRegistrationFailed,
1125 "dynamic client registration response did not include client_id"));
1129 *response, options.redirect_uri, std::move(options.scopes));
1130 configure_client(config);
1136 if (options.client_id_metadata_url.has_value() &&
1138 return configure_client_id_metadata_url(*options.client_id_metadata_url,
1139 std::move(options.redirect_uri),
1140 std::move(options.scopes));
1144 registration_options.client_name = std::move(options.client_name);
1145 registration_options.redirect_uri = std::move(options.redirect_uri);
1146 registration_options.scopes = std::move(options.scopes);
1147 registration_options.metadata = std::move(options.metadata);
1148 return register_client(std::move(registration_options),
1149 std::move(options.headers));
1154 if (client_.client_id.empty() ||
1155 client_.redirect_uri != request.client.redirect_uri) {
1156 auto configured = configure_client_for_authorization(request.client);
1157 if (!configured.has_value()) {
1158 return mcp::core::unexpected(configured.error());
1162 auto authorization = start_authorization(
1163 request.client.scopes, std::move(request.pkce),
1164 std::move(request.state),
1165 std::move(request.additional_authorization_parameters));
1166 if (!authorization.has_value()) {
1167 return mcp::core::unexpected(authorization.error());
1169 return OAuthSession{std::move(*authorization), client_.redirect_uri};
1174 MetadataMap additional_parameters = {}) {
1177 request.client = client_;
1178 request.authorization_server = metadata_;
1179 request.resource = resource_;
1180 request.scopes = std::move(scopes);
1181 request.pkce = std::move(pkce);
1182 request.state = std::move(state);
1183 request.additional_parameters = std::move(additional_parameters);
1186 if (!result.has_value()) {
1187 return mcp::core::unexpected(result.error());
1190 auto store_result = state_store().save(result->state.state, result->state);
1191 if (!store_result.has_value()) {
1192 return mcp::core::unexpected(store_result.error());
1194 state_ = OAuthLifecycleState::kAuthorizationPending;
1199 std::string authorization_code,
const std::string& state) {
1200 if (!token_endpoint_) {
1201 return mcp::core::unexpected(
1203 "OAuth token endpoint is not configured"));
1206 auto loaded_state = state_store().load(state);
1207 if (!loaded_state.has_value()) {
1208 return mcp::core::unexpected(loaded_state.error());
1210 if (!loaded_state->has_value()) {
1211 return mcp::core::unexpected(
1213 "authorization state was not found"));
1215 auto remove_result = state_store().remove(state);
1216 if (!remove_result.has_value()) {
1217 return mcp::core::unexpected(remove_result.error());
1220 auto stored_state = std::move(**loaded_state);
1221 const auto now = SystemClock::now();
1222 if (authorization_state_ttl_ <= std::chrono::seconds::zero() ||
1223 stored_state.created_at + authorization_state_ttl_ <= now) {
1224 return mcp::core::unexpected(
1226 "authorization state has expired"));
1230 request.client = client_;
1231 request.authorization_server = metadata_;
1232 request.resource = resource_;
1233 request.authorization_code = std::move(authorization_code);
1234 request.state = std::move(stored_state);
1236 auto token_result = token_endpoint_->exchange_authorization_code(request);
1237 if (!token_result.has_value()) {
1238 return mcp::core::unexpected(token_result.error());
1241 current_scopes_ = token_result->scopes.empty()
1242 ? request.state.requested_scopes
1243 : token_result->scopes;
1244 scope_upgrade_attempts_ = 0;
1246 credentials.client_id = client_.client_id;
1247 credentials.token_set = *token_result;
1248 credentials.granted_scopes = current_scopes_;
1249 credentials.token_received_at = SystemClock::now();
1251 credential_store().save(credential_key(), std::move(credentials));
1252 if (!save_result.has_value()) {
1253 return mcp::core::unexpected(save_result.error());
1256 state_ = OAuthLifecycleState::kAuthorized;
1257 return *token_result;
1261 if (!token_endpoint_) {
1262 return mcp::core::unexpected(
1264 "OAuth token endpoint is not configured"));
1267 auto loaded = credential_store().load(credential_key());
1268 if (!loaded.has_value()) {
1269 return mcp::core::unexpected(loaded.error());
1271 if (!loaded->has_value() || !(**loaded).token_set.has_value()) {
1272 return mcp::core::unexpected(
1274 "stored credentials are not available"));
1277 auto credentials = **loaded;
1278 const auto& current = *credentials.token_set;
1279 if (!current.refresh_token.has_value() || current.refresh_token->empty()) {
1280 return mcp::core::unexpected(
1282 "refresh token is not available"));
1286 request.client = client_;
1287 request.authorization_server = metadata_;
1288 request.resource = resource_;
1289 request.refresh_token = *current.refresh_token;
1290 request.scopes = credentials.granted_scopes;
1292 auto refreshed = token_endpoint_->refresh_access_token(request);
1293 if (!refreshed.has_value()) {
1294 return mcp::core::unexpected(refreshed.error());
1296 if (!refreshed->token_set.refresh_token.has_value()) {
1297 refreshed->token_set.refresh_token = current.refresh_token;
1298 refreshed->refresh_token_rotated =
false;
1300 refreshed->refresh_token_rotated =
1301 refreshed->token_set.refresh_token != current.refresh_token;
1304 current_scopes_ = refreshed->token_set.scopes.empty()
1305 ? credentials.granted_scopes
1306 : refreshed->token_set.scopes;
1307 credentials.token_set = refreshed->token_set;
1308 credentials.granted_scopes = current_scopes_;
1309 credentials.token_received_at = SystemClock::now();
1311 credential_store().save(credential_key(), std::move(credentials));
1312 if (!save_result.has_value()) {
1313 return mcp::core::unexpected(save_result.error());
1316 state_ = OAuthLifecycleState::kAuthorized;
1321 std::chrono::seconds refresh_skew = std::chrono::seconds(30)) {
1322 auto loaded = credential_store().load(credential_key());
1323 if (!loaded.has_value()) {
1324 return mcp::core::unexpected(loaded.error());
1326 if (!loaded->has_value() || !(**loaded).token_set.has_value()) {
1327 return mcp::core::unexpected(
1329 "stored credentials are not available"));
1332 const auto& token_set = *(**loaded).token_set;
1333 if (!token_set.expires_at.has_value() ||
1334 *token_set.expires_at > SystemClock::now() + refresh_skew) {
1335 return token_set.access_token;
1337 if (!token_set.refresh_token.has_value()) {
1339 OAuthErrorCode::kAuthorizationRequired,
1340 "access token is expired and no refresh token is available"));
1343 auto refreshed = refresh_access_token();
1344 if (!refreshed.has_value()) {
1345 return mcp::core::unexpected(refreshed.error());
1347 return refreshed->token_set.access_token;
1353 if (!decision.has_value()) {
1354 return mcp::core::unexpected(decision.error());
1358 retry.decision = std::move(*decision);
1359 if (response.status_code != 401 ||
1360 retry.decision.action != AuthResponseAction::kAuthorizationRequired) {
1364 auto refreshed = refresh_access_token();
1365 if (!refreshed.has_value()) {
1366 return mcp::core::unexpected(refreshed.error());
1369 retry.should_retry =
true;
1370 retry.bearer_token = refreshed->token_set.access_token;
1380 if (!token_endpoint_) {
1381 return mcp::core::unexpected(
1383 "OAuth token endpoint is not configured"));
1385 if (config.client_id.empty()) {
1387 OAuthErrorCode::kInvalidRequest,
"client_id is required"));
1389 if (config.client_secret.empty()) {
1391 OAuthErrorCode::kInvalidRequest,
"client_secret is required"));
1393 if (config.resource.empty()) {
1395 OAuthErrorCode::kInvalidRequest,
"resource is required"));
1398 const auto method_iter = config.metadata.find(
"token_endpoint_auth_method");
1399 const auto token_endpoint_auth_method =
1400 method_iter == config.metadata.end() || method_iter->second.empty()
1401 ? std::string_view(
"client_secret_post")
1402 : std::string_view(method_iter->second);
1404 metadata_, token_endpoint_auth_method);
1405 if (!validation.has_value()) {
1406 return mcp::core::unexpected(validation.error());
1412 request.credentials = std::move(config);
1413 request.authorization_server = metadata_;
1415 auto token_result = token_endpoint_->exchange_client_credentials(request);
1416 if (!token_result.has_value()) {
1417 return mcp::core::unexpected(token_result.error());
1420 client_.client_id = request.credentials.client_id;
1421 client_.client_secret = request.credentials.client_secret;
1422 resource_ = request.credentials.resource;
1424 current_scopes_ = token_result->scopes.empty() ? request.credentials.scopes
1425 : token_result->scopes;
1426 scope_upgrade_attempts_ = 0;
1429 credentials.client_id = request.credentials.client_id;
1430 credentials.token_set = *token_result;
1431 credentials.granted_scopes = current_scopes_;
1432 credentials.token_received_at = SystemClock::now();
1434 credential_store().save(credential_key(), std::move(credentials));
1435 if (!save_result.has_value()) {
1436 return mcp::core::unexpected(save_result.error());
1439 state_ = OAuthLifecycleState::kAuthorized;
1440 return *token_result;
1443 bool can_attempt_scope_upgrade()
const {
1444 return scope_upgrade_config_.auto_upgrade &&
1445 scope_upgrade_attempts_ < scope_upgrade_config_.max_upgrade_attempts;
1449 const WwwAuthenticateChallenge& challenge, PkceChallenge pkce,
1450 std::string state, MetadataMap additional_parameters = {}) {
1451 if (!insufficient_scope(challenge)) {
1452 return mcp::core::unexpected(make_oauth_error(
1453 OAuthErrorCode::kInsufficientScope,
1454 "WWW-Authenticate challenge is not insufficient_scope"));
1456 if (!can_attempt_scope_upgrade()) {
1459 "scope upgrade attempts are exhausted"));
1462 const auto required_scope =
1463 challenge.parameter(std::string(WwwAuthenticateScopeParam));
1464 const auto upgraded_scopes =
1465 compute_scope_union(current_scopes_, required_scope);
1466 ++scope_upgrade_attempts_;
1467 return start_authorization(upgraded_scopes, std::move(pkce),
1469 std::move(additional_parameters));
1472 static ScopeList compute_scope_union(
const ScopeList& current,
1473 std::string_view required_scope) {
1474 ScopeList result = current;
1475 for (
const auto& scope : detail::split_scopes(required_scope)) {
1476 if (!detail::has_scope(result, scope)) {
1477 result.push_back(scope);
1484 CredentialStore& credential_store() {
1485 if (!credential_store_) {
1486 credential_store_ = std::make_shared<InMemoryCredentialStore>();
1488 return *credential_store_;
1491 StateStore& state_store() {
1492 if (!state_store_) {
1493 state_store_ = std::make_shared<InMemoryStateStore>();
1495 return *state_store_;
1498 std::string resource_;
1499 AuthorizationServerMetadata metadata_;
1500 OAuthClientConfig client_;
1501 std::shared_ptr<CredentialStore> credential_store_;
1502 std::shared_ptr<StateStore> state_store_;
1503 std::shared_ptr<OAuthTokenEndpoint> token_endpoint_;
1504 std::shared_ptr<OAuthClientRegistrationEndpoint> registration_endpoint_;
1505 ScopeUpgradeConfig scope_upgrade_config_;
1507 ScopeList current_scopes_;
1508 std::uint32_t scope_upgrade_attempts_ = 0;
1509 std::chrono::seconds authorization_state_ttl_ = kDefaultAuthorizationStateTtl;