Pistache Auth

認証ライブラリC++HTTPREST APIPistacheWeb Server認証セキュリティ

認証ライブラリ

Pistache Auth

概要

Pistache Authは、高性能なC++ HTTPフレームワークPistacheを使用したREST API認証システムです。

詳細

Pistache Authは、Pistache HTTPフレームワークをベースにした認証システムの実装です。PistacheはC++17で書かれたモダンで高性能なHTTPおよびRESTフレームワークで、Apache License 2.0の下で提供されています。非同期APIを提供し、マルチスレッドHTTPサーバーでAPIを構築でき、HTTPクライアント、HTTPルーター、REST記述DSL、型安全なヘッダーとMIMEタイプ実装を含んでいます。HTTPSサポートによりSSL/TLS接続が可能で、セッションメカニズムやHTTP組み込み認証メカニズムによる認証が実装できます。ただし、プロジェクトはまだ1.0リリースに達しておらず、不安定だが使用可能な状態です。多くのコードは本番環境対応済みで、RESTful API開発には問題なく使用できますが、HTTPクライアントには一部バグがあります。

メリット・デメリット

メリット

  • 高性能: C++17による最適化されたパフォーマンス
  • 非同期処理: 非同期APIによる効率的なリクエスト処理
  • モダンAPI: 低レベルHTTP抽象化による明確で快適なAPI
  • HTTPS対応: 組み込みHTTPS機能による安全な通信
  • 型安全性: C++の型システムによる安全性確保
  • 軽量: 必要最小限の依存関係
  • REST特化: RESTful API構築に最適化

デメリット

  • プレリリース: 1.0未満のため不安定な部分あり
  • C++知識: C++17の深い理解が必要
  • 認証制限: OpenAPIコード生成で認証ロジックが不完全
  • ドキュメント: 詳細ドキュメントが限定的
  • コミュニティ: 比較的小さなコミュニティ
  • デバッグ難易度: C++レベルでのデバッグが必要

主要リンク

書き方の例

基本的なHTTPサーバー設定

// main.cpp
#include <pistache/net.h>
#include <pistache/http.h>
#include <pistache/peer.h>
#include <pistache/http_headers.h>
#include <pistache/cookie.h>
#include <pistache/router.h>
#include <pistache/endpoint.h>

using namespace Pistache;

class AuthHandler {
public:
    void onRequest(const Rest::Request& request, Http::ResponseWriter response) {
        // 基本認証チェック
        if (!checkBasicAuth(request)) {
            response.send(Http::Code::Unauthorized, "Authentication required");
            return;
        }
        
        response.send(Http::Code::Ok, "Hello, authenticated user!");
    }

private:
    bool checkBasicAuth(const Rest::Request& request) {
        auto auth_header = request.headers().tryGet<Http::Header::Authorization>();
        if (!auth_header) {
            return false;
        }
        
        // Basic認証の実装
        std::string auth_string = auth_header->value();
        return validateBasicAuth(auth_string);
    }
    
    bool validateBasicAuth(const std::string& auth_string) {
        // "Basic " プレフィックスをチェック
        if (auth_string.substr(0, 6) != "Basic ") {
            return false;
        }
        
        // Base64デコードとユーザー検証
        std::string credentials = decodeBase64(auth_string.substr(6));
        return validateCredentials(credentials);
    }
};

int main() {
    Http::Endpoint server(Address(Ipv4::any(), Port(8080)));
    auto opts = Http::Endpoint::options().threads(1);
    server.init(opts);
    
    AuthHandler handler;
    server.setHandler(Http::make_handler<Http::Handler>([&](const Http::Request& req, Http::ResponseWriter response) {
        handler.onRequest(req, std::move(response));
    }));
    
    server.serve();
}

JWTトークン認証

// jwt_auth.cpp
#include <pistache/router.h>
#include <jwt-cpp/jwt.h>
#include <nlohmann/json.hpp>

class JWTAuthenticator {
private:
    std::string secret_key;
    
public:
    JWTAuthenticator(const std::string& key) : secret_key(key) {}
    
    std::string generateToken(const std::string& username, const std::string& role) {
        auto token = jwt::create()
            .set_issuer("pistache-auth")
            .set_type("JWT")
            .set_payload_claim("username", jwt::claim(username))
            .set_payload_claim("role", jwt::claim(role))
            .set_issued_at(std::chrono::system_clock::now())
            .set_expires_at(std::chrono::system_clock::now() + std::chrono::hours{24})
            .sign(jwt::algorithm::hs256{secret_key});
        
        return token;
    }
    
    bool validateToken(const std::string& token, std::string& username) {
        try {
            auto decoded = jwt::decode(token);
            auto verifier = jwt::verify()
                .allow_algorithm(jwt::algorithm::hs256{secret_key})
                .with_issuer("pistache-auth");
            
            verifier.verify(decoded);
            username = decoded.get_payload_claim("username").as_string();
            return true;
        } catch (const std::exception& e) {
            return false;
        }
    }
};

class SecureEndpoint {
private:
    JWTAuthenticator authenticator;
    
public:
    SecureEndpoint(const std::string& secret) : authenticator(secret) {}
    
    void login(const Rest::Request& request, Http::ResponseWriter response) {
        auto json_body = nlohmann::json::parse(request.body());
        std::string username = json_body["username"];
        std::string password = json_body["password"];
        
        if (validateCredentials(username, password)) {
            std::string token = authenticator.generateToken(username, "user");
            nlohmann::json result = {
                {"token", token},
                {"message", "Login successful"}
            };
            
            response.headers().add<Http::Header::ContentType>(MIME(Application, Json));
            response.send(Http::Code::Ok, result.dump());
        } else {
            response.send(Http::Code::Unauthorized, "Invalid credentials");
        }
    }
    
    void protectedRoute(const Rest::Request& request, Http::ResponseWriter response) {
        auto auth_header = request.headers().tryGet<Http::Header::Authorization>();
        if (!auth_header) {
            response.send(Http::Code::Unauthorized, "Authorization header missing");
            return;
        }
        
        std::string auth_value = auth_header->value();
        if (auth_value.substr(0, 7) != "Bearer ") {
            response.send(Http::Code::Unauthorized, "Invalid authorization format");
            return;
        }
        
        std::string token = auth_value.substr(7);
        std::string username;
        
        if (authenticator.validateToken(token, username)) {
            nlohmann::json result = {
                {"message", "Access granted"},
                {"user", username}
            };
            
            response.headers().add<Http::Header::ContentType>(MIME(Application, Json));
            response.send(Http::Code::Ok, result.dump());
        } else {
            response.send(Http::Code::Unauthorized, "Invalid token");
        }
    }
    
private:
    bool validateCredentials(const std::string& username, const std::string& password) {
        // データベースやファイルからユーザー情報を検証
        return (username == "admin" && password == "password123");
    }
};

ルーター設定とミドルウェア

// router_auth.cpp
#include <pistache/router.h>

class AuthMiddleware {
public:
    static void authRequired(const Rest::Request& request, 
                           Http::ResponseWriter response, 
                           Rest::Route::Result& result) {
        auto auth_header = request.headers().tryGet<Http::Header::Authorization>();
        if (!auth_header) {
            response.send(Http::Code::Unauthorized, "Authentication required");
            result = Rest::Route::Result::Failure;
            return;
        }
        
        // トークン検証ロジック
        if (!validateAuthToken(auth_header->value())) {
            response.send(Http::Code::Forbidden, "Invalid authentication");
            result = Rest::Route::Result::Failure;
            return;
        }
        
        result = Rest::Route::Result::Ok;
    }
    
private:
    static bool validateAuthToken(const std::string& token) {
        // トークン検証実装
        return !token.empty();
    }
};

class ApiServer {
private:
    std::shared_ptr<Http::Endpoint> httpEndpoint;
    Rest::Router router;
    
public:
    explicit ApiServer(Address addr) 
        : httpEndpoint(std::make_shared<Http::Endpoint>(addr)) {}
    
    void init(size_t thr = 2) {
        auto opts = Http::Endpoint::options()
            .threads(static_cast<int>(thr));
        httpEndpoint->init(opts);
        setupRoutes();
    }
    
    void start() {
        httpEndpoint->setHandler(router.handler());
        httpEndpoint->serve();
    }
    
private:
    void setupRoutes() {
        using namespace Rest;
        
        // 公開エンドポイント
        Routes::Post(router, "/auth/login", Routes::bind(&ApiServer::login, this));
        Routes::Post(router, "/auth/register", Routes::bind(&ApiServer::registerUser, this));
        
        // 認証が必要なエンドポイント(ミドルウェア適用)
        auto protectedGroup = router.group("/api");
        protectedGroup.addMiddleware(AuthMiddleware::authRequired);
        
        Routes::Get(protectedGroup, "/profile", Routes::bind(&ApiServer::getProfile, this));
        Routes::Put(protectedGroup, "/profile", Routes::bind(&ApiServer::updateProfile, this));
        Routes::Delete(protectedGroup, "/account", Routes::bind(&ApiServer::deleteAccount, this));
    }
    
    void login(const Rest::Request& request, Http::ResponseWriter response) {
        // ログイン処理
        response.send(Http::Code::Ok, "Login endpoint");
    }
    
    void registerUser(const Rest::Request& request, Http::ResponseWriter response) {
        // ユーザー登録処理
        response.send(Http::Code::Created, "User registered");
    }
    
    void getProfile(const Rest::Request& request, Http::ResponseWriter response) {
        // プロファイル取得(認証済み)
        response.send(Http::Code::Ok, "User profile data");
    }
    
    void updateProfile(const Rest::Request& request, Http::ResponseWriter response) {
        // プロファイル更新(認証済み)
        response.send(Http::Code::Ok, "Profile updated");
    }
    
    void deleteAccount(const Rest::Request& request, Http::ResponseWriter response) {
        // アカウント削除(認証済み)
        response.send(Http::Code::Ok, "Account deleted");
    }
};

HTTPS設定とセキュリティ

// https_server.cpp
#include <pistache/endpoint.h>
#include <pistache/http.h>
#include <pistache/ssl.h>

class SecureServer {
private:
    Http::Endpoint endpoint;
    
public:
    SecureServer(const Address& addr) : endpoint(addr) {}
    
    void init() {
        // HTTPS設定
        auto opts = Http::Endpoint::options()
            .threads(4)
            .maxRequestSize(1024 * 1024)  // 1MB
            .maxResponseSize(1024 * 1024); // 1MB
            
        // SSL/TLS設定
        if (enableSSL()) {
            opts.enableSSL("server.crt", "server.key");
        }
        
        endpoint.init(opts);
        setupSecurityHeaders();
    }
    
    void start() {
        endpoint.serve();
    }
    
private:
    bool enableSSL() {
        // SSL証明書の存在確認
        return std::filesystem::exists("server.crt") && 
               std::filesystem::exists("server.key");
    }
    
    void setupSecurityHeaders() {
        endpoint.setHandler(Http::make_handler<Http::Handler>([this](const Http::Request& req, Http::ResponseWriter response) {
            // セキュリティヘッダーの追加
            response.headers().add<Http::Header::StrictTransportSecurity>("max-age=31536000; includeSubDomains");
            response.headers().add<Http::Header::ContentSecurityPolicy>("default-src 'self'");
            response.headers().addRaw("X-Frame-Options", "DENY");
            response.headers().addRaw("X-Content-Type-Options", "nosniff");
            response.headers().addRaw("X-XSS-Protection", "1; mode=block");
            
            handleRequest(req, std::move(response));
        }));
    }
    
    void handleRequest(const Http::Request& request, Http::ResponseWriter response) {
        // リクエスト処理ロジック
        response.send(Http::Code::Ok, "Secure response");
    }
};

int main() {
    Address addr(Ipv4::loopback(), Port(8443));
    SecureServer server(addr);
    
    server.init();
    std::cout << "Secure server listening on https://localhost:8443" << std::endl;
    server.start();
    
    return 0;
}

セッション管理

// session_manager.cpp
#include <unordered_map>
#include <random>
#include <sstream>

class SessionManager {
private:
    std::unordered_map<std::string, SessionData> sessions;
    std::random_device rd;
    std::mt19937 gen;
    
    struct SessionData {
        std::string username;
        std::chrono::system_clock::time_point created_at;
        std::chrono::system_clock::time_point last_access;
        std::unordered_map<std::string, std::string> data;
    };
    
public:
    SessionManager() : gen(rd()) {}
    
    std::string createSession(const std::string& username) {
        std::string session_id = generateSessionId();
        SessionData session;
        session.username = username;
        session.created_at = std::chrono::system_clock::now();
        session.last_access = session.created_at;
        
        sessions[session_id] = session;
        return session_id;
    }
    
    bool validateSession(const std::string& session_id) {
        auto it = sessions.find(session_id);
        if (it == sessions.end()) {
            return false;
        }
        
        // セッション有効期限チェック(24時間)
        auto now = std::chrono::system_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::hours>(
            now - it->second.created_at);
            
        if (duration.count() > 24) {
            sessions.erase(it);
            return false;
        }
        
        // 最後のアクセス時間更新
        it->second.last_access = now;
        return true;
    }
    
    void destroySession(const std::string& session_id) {
        sessions.erase(session_id);
    }
    
private:
    std::string generateSessionId() {
        std::uniform_int_distribution<> dis(0, 15);
        std::stringstream ss;
        
        for (int i = 0; i < 32; ++i) {
            ss << std::hex << dis(gen);
        }
        
        return ss.str();
    }
};