C++ REST SDK OAuth2

認証ライブラリC++OAuth2RESTMicrosoftHTTP-Clientセキュリティ

認証ライブラリ

C++ REST SDK OAuth2

概要

C++ REST SDK OAuth2は、Microsoft C++ REST SDK(cpprestsdk)に含まれるOAuth 2.0認証機能です。2025年現在、このSDKはメンテナンスモードに移行しており、Microsoftは新規プロジェクトでの使用を推奨していませんが、既存システムでは重要なバグ修正とセキュリティ更新が継続されています。C++アプリケーションでOAuth 2.0認証を実装するための数少ない選択肢の一つとして、認可コードフロー、暗黙的フロー、クライアント認証などの標準的なOAuth 2.0機能を提供します。web::http::oauth2::experimental名前空間で提供され、oauth2_config、oauth2_token、oauth2_exceptionクラスを通じてOAuth 2.0の設定、トークン管理、エラーハンドリングを行います。

詳細

C++ REST SDK OAuth2は、C++環境でOAuth 2.0認証を実装するためのライブラリです。主な特徴:

  • OAuth 2.0標準対応: RFC 6749に準拠した認可コードフロー、暗黙的フローの実装
  • 実験的API: web::http::oauth2::experimental名前空間での提供(APIの安定性に注意が必要)
  • 設定管理: oauth2_configクラスによるOAuth 2.0設定の管理
  • トークン管理: oauth2_tokenクラスによるアクセストークン、リフレッシュトークンの処理
  • クロスプラットフォーム: Windows、macOS、Linuxでの動作サポート
  • Microsoft Graph統合: Microsoft Graph APIとの連携に適した設計
  • HTTPクライアント統合: cpprestsdkのHTTPクライアント機能との統合

メリット・デメリット

メリット

  • C++ネイティブ環境でのOAuth 2.0実装、他言語ライブラリへの依存なし
  • Microsoft製品・サービスとの高い親和性とサポート
  • クロスプラットフォーム対応により、複数OS環境での一貫した実装
  • HTTP操作とOAuth認証の統合により、Web API連携が簡潔
  • 既存のcpprestsdkプロジェクトへの組み込みが容易
  • Microsoft Graph、Azure、Office 365などMicrosoft エコシステムとの連携に最適

デメリット

  • メンテナンスモードのため新機能開発は停止、セキュリティ更新のみ継続
  • 実験的APIのため将来的な破壊的変更のリスク
  • Microsoftが新規プロジェクトでの使用を推奨しない
  • OAuth 2.1やOpenID Connectなど最新認証仕様への対応なし
  • ドキュメントとコミュニティサポートが限定的
  • C++言語の学習コストとメモリ管理の複雑さ
  • 代替ライブラリが少なく、移行選択肢が限定的

参考ページ

書き方の例

基本的なOAuth2設定とクライアント実装

#include <cpprest/http_client.h>
#include <cpprest/oauth2.h>
#include <iostream>
#include <string>

using namespace web;
using namespace web::http;
using namespace web::http::client;
using namespace web::http::oauth2::experimental;

class OAuth2Client {
private:
    oauth2_config m_config;
    oauth2_token m_token;
    http_client m_http_client;
    
public:
    OAuth2Client(const std::string& client_id, 
                 const std::string& client_secret,
                 const std::string& auth_endpoint,
                 const std::string& token_endpoint,
                 const std::string& redirect_uri)
        : m_http_client(U("https://api.example.com")) {
        
        // OAuth2設定の初期化
        m_config.set_client_key(utility::conversions::to_string_t(client_id));
        m_config.set_client_secret(utility::conversions::to_string_t(client_secret));
        m_config.set_auth_endpoint(utility::conversions::to_string_t(auth_endpoint));
        m_config.set_token_endpoint(utility::conversions::to_string_t(token_endpoint));
        m_config.set_redirect_uri(utility::conversions::to_string_t(redirect_uri));
        
        // スコープの設定
        std::vector<utility::string_t> scopes = {
            U("read"),
            U("write"),
            U("profile")
        };
        m_config.set_scope(scopes);
    }
    
    // 認可URLの生成
    std::string build_authorization_uri() {
        auto auth_uri = m_config.build_authorization_uri(true); // PKCE有効
        return utility::conversions::to_utf8string(auth_uri);
    }
    
    // 認可コードからアクセストークンを取得
    pplx::task<bool> request_token(const std::string& authorization_code) {
        utility::string_t code = utility::conversions::to_string_t(authorization_code);
        
        return m_config.token_from_code(code)
        .then([this](oauth2_token token) {
            m_token = token;
            std::wcout << L"Access token obtained: " 
                      << token.access_token().substr(0, 20) << L"..." << std::endl;
            return true;
        })
        .then([](pplx::task<bool> t) {
            try {
                return t.get();
            }
            catch (const oauth2_exception& ex) {
                std::wcout << L"OAuth2 error: " << ex.what() << std::endl;
                return false;
            }
            catch (const std::exception& ex) {
                std::wcout << L"Error: " << utility::conversions::to_string_t(ex.what()) << std::endl;
                return false;
            }
        });
    }
    
    // リフレッシュトークンを使用してアクセストークンを更新
    pplx::task<bool> refresh_token() {
        if (m_token.refresh_token().empty()) {
            std::wcout << L"No refresh token available" << std::endl;
            return pplx::task_from_result(false);
        }
        
        return m_config.token_from_refresh(m_token)
        .then([this](oauth2_token new_token) {
            m_token = new_token;
            std::wcout << L"Token refreshed successfully" << std::endl;
            return true;
        })
        .then([](pplx::task<bool> t) {
            try {
                return t.get();
            }
            catch (const oauth2_exception& ex) {
                std::wcout << L"Token refresh error: " << ex.what() << std::endl;
                return false;
            }
        });
    }
    
    // 認証されたHTTPリクエストの送信
    pplx::task<http_response> make_authenticated_request(const std::string& path, 
                                                        method request_method = methods::GET) {
        http_request request(request_method);
        request.set_request_uri(utility::conversions::to_string_t(path));
        
        // アクセストークンをAuthorizationヘッダーに追加
        utility::string_t auth_header = U("Bearer ") + m_token.access_token();
        request.headers().add(header_names::authorization, auth_header);
        request.headers().add(header_names::content_type, U("application/json"));
        
        return m_http_client.request(request)
        .then([this](http_response response) {
            if (response.status_code() == status_codes::Unauthorized) {
                std::wcout << L"Token expired, attempting refresh..." << std::endl;
                return refresh_token()
                .then([this, request](bool success) {
                    if (success) {
                        // トークン更新後に再試行
                        http_request retry_request = request;
                        utility::string_t new_auth_header = U("Bearer ") + m_token.access_token();
                        retry_request.headers().remove(header_names::authorization);
                        retry_request.headers().add(header_names::authorization, new_auth_header);
                        return m_http_client.request(retry_request);
                    } else {
                        throw oauth2_exception("Failed to refresh token");
                    }
                });
            }
            return pplx::task_from_result(response);
        });
    }
    
    // トークンの有効性チェック
    bool is_token_valid() const {
        if (m_token.access_token().empty()) {
            return false;
        }
        
        // トークンの有効期限チェック(簡易版)
        auto now = utility::datetime::utc_now();
        return m_token.expires_in() > 0;
    }
    
    // トークン情報の表示
    void print_token_info() const {
        std::wcout << L"Token Type: " << m_token.token_type() << std::endl;
        std::wcout << L"Access Token: " << m_token.access_token().substr(0, 20) << L"..." << std::endl;
        std::wcout << L"Expires In: " << m_token.expires_in() << L" seconds" << std::endl;
        std::wcout << L"Scope: " << m_token.scope() << std::endl;
        
        if (!m_token.refresh_token().empty()) {
            std::wcout << L"Refresh Token: " << m_token.refresh_token().substr(0, 20) << L"..." << std::endl;
        }
    }
};

Microsoft Graph API連携の実装

#include <cpprest/http_client.h>
#include <cpprest/oauth2.h>
#include <cpprest/json.h>
#include <iostream>

using namespace web;
using namespace web::json;
using namespace web::http;
using namespace web::http::client;
using namespace web::http::oauth2::experimental;

class MicrosoftGraphClient {
private:
    oauth2_config m_config;
    oauth2_token m_token;
    http_client m_graph_client;
    
public:
    MicrosoftGraphClient(const std::string& client_id, 
                        const std::string& client_secret,
                        const std::string& tenant_id = "common")
        : m_graph_client(U("https://graph.microsoft.com/v1.0")) {
        
        // Microsoft Azure AD v2.0エンドポイント設定
        utility::string_t tenant = utility::conversions::to_string_t(tenant_id);
        utility::string_t auth_endpoint = U("https://login.microsoftonline.com/") + tenant + U("/oauth2/v2.0/authorize");
        utility::string_t token_endpoint = U("https://login.microsoftonline.com/") + tenant + U("/oauth2/v2.0/token");
        
        m_config.set_client_key(utility::conversions::to_string_t(client_id));
        m_config.set_client_secret(utility::conversions::to_string_t(client_secret));
        m_config.set_auth_endpoint(auth_endpoint);
        m_config.set_token_endpoint(token_endpoint);
        m_config.set_redirect_uri(U("http://localhost:8080/auth/callback"));
        
        // Microsoft Graph用スコープ設定
        std::vector<utility::string_t> scopes = {
            U("https://graph.microsoft.com/User.Read"),
            U("https://graph.microsoft.com/Mail.Read"),
            U("https://graph.microsoft.com/Files.Read")
        };
        m_config.set_scope(scopes);
    }
    
    // ユーザープロフィール取得
    pplx::task<json::value> get_user_profile() {
        return make_graph_request("/me")
        .then([](http_response response) {
            return response.extract_json();
        })
        .then([](json::value profile) {
            std::wcout << L"User Profile Retrieved:" << std::endl;
            std::wcout << L"Name: " << profile[U("displayName")].as_string() << std::endl;
            std::wcout << L"Email: " << profile[U("mail")].as_string() << std::endl;
            std::wcout << L"ID: " << profile[U("id")].as_string() << std::endl;
            return profile;
        });
    }
    
    // メール一覧取得
    pplx::task<json::value> get_user_messages(int limit = 10) {
        utility::string_t path = U("/me/messages?$top=") + utility::conversions::to_string_t(std::to_string(limit));
        
        return make_graph_request(utility::conversions::to_utf8string(path))
        .then([](http_response response) {
            return response.extract_json();
        })
        .then([](json::value messages) {
            std::wcout << L"Messages Retrieved:" << std::endl;
            auto message_array = messages[U("value")].as_array();
            
            for (size_t i = 0; i < message_array.size(); ++i) {
                auto message = message_array[i];
                std::wcout << L"Subject: " << message[U("subject")].as_string() << std::endl;
                std::wcout << L"From: " << message[U("from")][U("emailAddress")][U("name")].as_string() << std::endl;
                std::wcout << L"---" << std::endl;
            }
            
            return messages;
        });
    }
    
    // OneDriveファイル一覧取得
    pplx::task<json::value> get_drive_items() {
        return make_graph_request("/me/drive/root/children")
        .then([](http_response response) {
            return response.extract_json();
        })
        .then([](json::value files) {
            std::wcout << L"Drive Files:" << std::endl;
            auto file_array = files[U("value")].as_array();
            
            for (size_t i = 0; i < file_array.size(); ++i) {
                auto file = file_array[i];
                std::wcout << L"Name: " << file[U("name")].as_string();
                
                if (file.has_field(U("size"))) {
                    std::wcout << L" (" << file[U("size")].as_number().to_uint64() << L" bytes)";
                }
                std::wcout << std::endl;
            }
            
            return files;
        });
    }
    
    // 認証されたGraph APIリクエスト
    pplx::task<http_response> make_graph_request(const std::string& path, 
                                                method request_method = methods::GET) {
        if (!is_token_valid()) {
            throw oauth2_exception("Invalid or expired token");
        }
        
        http_request request(request_method);
        request.set_request_uri(utility::conversions::to_string_t(path));
        
        // Microsoft Graph用ヘッダー設定
        utility::string_t auth_header = U("Bearer ") + m_token.access_token();
        request.headers().add(header_names::authorization, auth_header);
        request.headers().add(header_names::content_type, U("application/json"));
        request.headers().add(U("Accept"), U("application/json"));
        
        return m_graph_client.request(request)
        .then([](http_response response) {
            if (response.status_code() != status_codes::OK) {
                std::wcout << L"Graph API request failed: " << response.status_code() << std::endl;
                return response.extract_json().then([response](json::value error_json) {
                    std::wcout << L"Error details: " << error_json.serialize() << std::endl;
                    throw http_exception(response.status_code());
                    return response;
                });
            }
            return pplx::task_from_result(response);
        });
    }
    
    // OAuth2Clientクラスのメソッドを継承または委譲で利用
    bool is_token_valid() const {
        return !m_token.access_token().empty() && m_token.expires_in() > 0;
    }
    
    void set_token(const oauth2_token& token) {
        m_token = token;
    }
    
    oauth2_config& get_config() {
        return m_config;
    }
};

完全な認証フローとエラーハンドリング

#include <cpprest/http_listener.h>
#include <cpprest/uri_builder.h>
#include <thread>
#include <future>

using namespace web::http::experimental::listener;

class OAuth2FlowManager {
private:
    std::unique_ptr<http_listener> m_listener;
    std::promise<std::string> m_auth_code_promise;
    std::future<std::string> m_auth_code_future;
    
public:
    OAuth2FlowManager() : m_auth_code_future(m_auth_code_promise.get_future()) {}
    
    // ローカルHTTPサーバーを起動してコールバックを待機
    void start_callback_server(int port = 8080) {
        utility::string_t address = U("http://localhost:") + utility::conversions::to_string_t(std::to_string(port));
        m_listener = std::make_unique<http_listener>(address);
        
        m_listener->support(methods::GET, [this](http_request request) {
            auto query_params = uri::split_query(request.request_uri().query());
            auto code_it = query_params.find(U("code"));
            auto error_it = query_params.find(U("error"));
            
            if (error_it != query_params.end()) {
                std::string error_msg = utility::conversions::to_utf8string(error_it->second);
                std::wcout << L"OAuth error: " << error_it->second << std::endl;
                m_auth_code_promise.set_exception(std::make_exception_ptr(
                    oauth2_exception(error_msg.c_str())));
            } else if (code_it != query_params.end()) {
                std::string auth_code = utility::conversions::to_utf8string(code_it->second);
                std::wcout << L"Authorization code received: " << code_it->second.substr(0, 10) << L"..." << std::endl;
                m_auth_code_promise.set_value(auth_code);
            } else {
                m_auth_code_promise.set_exception(std::make_exception_ptr(
                    oauth2_exception("No authorization code received")));
            }
            
            // 成功ページを返送
            utility::string_t response_body = U(
                "<html><body><h2>Authorization completed!</h2>"
                "<p>You can close this window.</p></body></html>"
            );
            request.reply(status_codes::OK, response_body, U("text/html"));
        });
        
        m_listener->open().wait();
        std::wcout << L"Callback server started on " << address << std::endl;
    }
    
    // 完全な認証フローの実行
    pplx::task<oauth2_token> complete_auth_flow(OAuth2Client& client) {
        return pplx::create_task([this, &client]() {
            // ステップ1: ローカルサーバー起動
            start_callback_server();
            
            // ステップ2: 認可URL生成と表示
            std::string auth_url = client.build_authorization_uri();
            std::wcout << L"Please visit the following URL to authorize the application:" << std::endl;
            std::wcout << utility::conversions::to_string_t(auth_url) << std::endl;
            std::wcout << L"Waiting for authorization..." << std::endl;
            
            // ステップ3: ユーザーの認可完了を待機
            std::string auth_code;
            try {
                // タイムアウト付きで認可コードを待機(5分間)
                auto timeout = std::chrono::minutes(5);
                if (m_auth_code_future.wait_for(timeout) == std::future_status::timeout) {
                    throw oauth2_exception("Authorization timeout");
                }
                auth_code = m_auth_code_future.get();
            } catch (const std::exception& ex) {
                m_listener->close().wait();
                throw;
            }
            
            // ステップ4: 認可コードをアクセストークンに交換
            return client.request_token(auth_code).then([this](bool success) {
                m_listener->close().wait();
                if (!success) {
                    throw oauth2_exception("Failed to exchange authorization code for token");
                }
                return oauth2_token(); // 実際のトークンを返す(要実装)
            });
        });
    }
    
    ~OAuth2FlowManager() {
        if (m_listener) {
            m_listener->close().wait();
        }
    }
};

// 使用例とエラーハンドリング
int main() {
    try {
        // OAuth2クライアント設定
        OAuth2Client client(
            "your-client-id",
            "your-client-secret", 
            "https://accounts.google.com/o/oauth2/v2/auth",
            "https://oauth2.googleapis.com/token",
            "http://localhost:8080/auth/callback"
        );
        
        OAuth2FlowManager flow_manager;
        
        // 完全な認証フローを実行
        auto token_task = flow_manager.complete_auth_flow(client);
        
        token_task.then([&client](oauth2_token token) {
            std::wcout << L"Authentication successful!" << std::endl;
            client.print_token_info();
            
            // 認証されたAPIリクエストのテスト
            return client.make_authenticated_request("/api/user/profile");
        })
        .then([](http_response response) {
            return response.extract_json();
        })
        .then([](json::value profile_data) {
            std::wcout << L"Profile data: " << profile_data.serialize() << std::endl;
        })
        .then([](pplx::task<void> t) {
            try {
                t.get();
            } catch (const oauth2_exception& ex) {
                std::wcout << L"OAuth2 error: " << ex.what() << std::endl;
            } catch (const http_exception& ex) {
                std::wcout << L"HTTP error: " << ex.what() << L" (" << ex.error_code() << L")" << std::endl;
            } catch (const std::exception& ex) {
                std::wcout << L"General error: " << utility::conversions::to_string_t(ex.what()) << std::endl;
            }
        }).wait();
        
    } catch (const std::exception& ex) {
        std::wcout << L"Application error: " << utility::conversions::to_string_t(ex.what()) << std::endl;
        return 1;
    }
    
    return 0;
}

トークン永続化と自動更新

#include <fstream>
#include <cpprest/json.h>

class TokenStorage {
public:
    // トークンをファイルに保存
    static bool save_token(const oauth2_token& token, const std::string& filename = "oauth_token.json") {
        try {
            json::value token_json = json::value::object();
            token_json[U("access_token")] = json::value::string(token.access_token());
            token_json[U("refresh_token")] = json::value::string(token.refresh_token());
            token_json[U("token_type")] = json::value::string(token.token_type());
            token_json[U("expires_in")] = json::value::number(token.expires_in());
            token_json[U("scope")] = json::value::string(token.scope());
            
            std::ofstream file(filename);
            file << utility::conversions::to_utf8string(token_json.serialize());
            file.close();
            
            std::wcout << L"Token saved to " << utility::conversions::to_string_t(filename) << std::endl;
            return true;
        } catch (const std::exception& ex) {
            std::wcout << L"Error saving token: " << utility::conversions::to_string_t(ex.what()) << std::endl;
            return false;
        }
    }
    
    // ファイルからトークンを復元
    static std::unique_ptr<oauth2_token> load_token(const std::string& filename = "oauth_token.json") {
        try {
            std::ifstream file(filename);
            if (!file.is_open()) {
                std::wcout << L"Token file not found: " << utility::conversions::to_string_t(filename) << std::endl;
                return nullptr;
            }
            
            std::string json_string((std::istreambuf_iterator<char>(file)),
                                   std::istreambuf_iterator<char>());
            file.close();
            
            auto token_json = json::value::parse(utility::conversions::to_string_t(json_string));
            
            // oauth2_tokenの再構築(実際の実装はライブラリのバージョンに依存)
            auto token = std::make_unique<oauth2_token>();
            // 注意: oauth2_tokenクラスの実際のセッター方法は実装依存
            
            std::wcout << L"Token loaded from " << utility::conversions::to_string_t(filename) << std::endl;
            return token;
        } catch (const std::exception& ex) {
            std::wcout << L"Error loading token: " << utility::conversions::to_string_t(ex.what()) << std::endl;
            return nullptr;
        }
    }
    
    // トークンの有効期限チェック(時刻ベース)
    static bool is_token_expired(const oauth2_token& token) {
        // 実装注意: oauth2_tokenクラスには有効期限の絶対時刻情報がない場合がある
        // その場合、expires_inと発行時刻を別途管理する必要がある
        return token.expires_in() <= 0;
    }
};

// 自動トークン管理クラス
class AutoRefreshOAuth2Client : public OAuth2Client {
public:
    AutoRefreshOAuth2Client(const std::string& client_id, 
                           const std::string& client_secret,
                           const std::string& auth_endpoint,
                           const std::string& token_endpoint,
                           const std::string& redirect_uri)
        : OAuth2Client(client_id, client_secret, auth_endpoint, token_endpoint, redirect_uri) {
        
        // 保存されたトークンの復元を試行
        auto saved_token = TokenStorage::load_token();
        if (saved_token && !TokenStorage::is_token_expired(*saved_token)) {
            // トークンが有効な場合は使用
            set_token(*saved_token);
            std::wcout << L"Using saved token" << std::endl;
        }
    }
    
    // 認証されたリクエスト(自動更新付き)
    pplx::task<http_response> make_request_with_auto_refresh(const std::string& path, 
                                                           method request_method = methods::GET) {
        return make_authenticated_request(path, request_method)
        .then([this](pplx::task<http_response> response_task) {
            try {
                auto response = response_task.get();
                
                // リクエスト成功時はトークンを保存
                if (response.status_code() == status_codes::OK) {
                    TokenStorage::save_token(get_current_token());
                }
                
                return response;
            } catch (const oauth2_exception& ex) {
                std::wcout << L"Request failed, clearing saved token" << std::endl;
                TokenStorage::save_token(oauth2_token()); // 空のトークンで上書き
                throw;
            }
        });
    }
    
private:
    oauth2_token get_current_token() const {
        // 現在のトークンを取得(実装はprivateメンバーアクセスに依存)
        return oauth2_token(); // プレースホルダー
    }
    
    void set_token(const oauth2_token& token) {
        // トークンを設定(実装はprivateメンバーアクセスに依存)
    }
};