Sebelum memulai praktik, pastikan Anda telah memahami konsep dasar pada materi kelas AI Agent & PHP di infokoding.

BAB 17

Menambahkan Authentication

Mengamankan AI Agent PHP dengan sistem authentication yang kuat: login user, API Key per-user, JWT authentication, dan rate limiting untuk mencegah penyalahgunaan.

Terakhir diperbarui:

TL;DR (Ringkasan Cepat)

  • API Key cocok untuk komunikasi server-to-server.
  • JWT cocok untuk autentikasi user di web/mobile app.
  • Rate Limiting wajib ada untuk mencegah penyalahgunaan dan tagihan API berlebih.
  • Simpan password dengan password_hash(PASSWORD_BCRYPT) dan verifikasi dengan password_verify().

Database untuk Authentication

CREATE TABLE users (
    id         BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    name       VARCHAR(100) NOT NULL,
    email      VARCHAR(150) UNIQUE NOT NULL,
    password   VARCHAR(255) NOT NULL,
    api_key    VARCHAR(64) UNIQUE NULL,
    role       ENUM('user','admin') DEFAULT 'user',
    is_active  TINYINT(1) DEFAULT 1,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB;

CREATE TABLE rate_limits (
    id         BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    identifier VARCHAR(100) NOT NULL,
    requests   INT DEFAULT 0,
    window_end DATETIME NOT NULL,
    INDEX idx_identifier (identifier)
) ENGINE=InnoDB;

API Key Authentication

<?php

namespace App\Auth;

class ApiKeyAuth
{
    private PDO $pdo;

    public function __construct(PDO $pdo)
    {
        $this->pdo = $pdo;
    }

    public function authenticate(string $apiKey): ?array
    {
        if (empty($apiKey) || strlen($apiKey) < 32) {
            return null;
        }

        $stmt = $this->pdo->prepare(
            'SELECT id, name, email, role FROM users
             WHERE api_key = ? AND is_active = 1 LIMIT 1'
        );
        $stmt->execute([$apiKey]);
        return $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
    }

    public function generateApiKey(int $userId): string
    {
        $apiKey = bin2hex(random_bytes(32)); // 64 karakter hex
        $stmt   = $this->pdo->prepare('UPDATE users SET api_key = ? WHERE id = ?');
        $stmt->execute([$apiKey, $userId]);
        return $apiKey;
    }
}

Rate Limiter

<?php

namespace App\Auth;

class RateLimiter
{
    private PDO $pdo;
    private int $maxRequests;
    private int $windowSeconds;

    public function __construct(PDO $pdo, int $maxRequests = 20, int $windowSeconds = 60)
    {
        $this->pdo           = $pdo;
        $this->maxRequests   = $maxRequests;
        $this->windowSeconds = $windowSeconds;
    }

    public function isAllowed(string $identifier): bool
    {
        $now      = time();
        $windowEnd = date('Y-m-d H:i:s', $now + $this->windowSeconds);

        // Bersihkan entri yang sudah kadaluarsa
        $this->pdo->prepare('DELETE FROM rate_limits WHERE window_end < NOW()')
                  ->execute();

        $stmt = $this->pdo->prepare(
            'SELECT id, requests FROM rate_limits WHERE identifier = ? AND window_end > NOW() LIMIT 1'
        );
        $stmt->execute([$identifier]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);

        if (!$row) {
            // Buat entri baru
            $this->pdo->prepare(
                'INSERT INTO rate_limits (identifier, requests, window_end) VALUES (?, 1, ?)')
                ->execute([$identifier, $windowEnd]);
            return true;
        }

        if ($row['requests'] >= $this->maxRequests) {
            return false; // Rate limit tercapai
        }

        // Increment counter
        $this->pdo->prepare('UPDATE rate_limits SET requests = requests + 1 WHERE id = ?')
                  ->execute([$row['id']]);
        return true;
    }
}

Middleware Authentication di Agent Endpoint

<?php
// api/agent.php
header('Content-Type: application/json');

$apiKey = $_SERVER['HTTP_X_API_KEY'] ?? '';
$auth   = new App\Auth\ApiKeyAuth($pdo);
$user   = $auth->authenticate($apiKey);

if (!$user) {
    http_response_code(401);
    echo json_encode(['error' => 'API Key tidak valid atau tidak ditemukan']);
    exit;
}

// Rate limiting
$limiter = new App\Auth\RateLimiter($pdo, maxRequests: 20, windowSeconds: 60);
if (!$limiter->isAllowed("user:{$user['id']}")) {
    http_response_code(429);
    echo json_encode(['error' => 'Rate limit tercapai. Coba lagi dalam 60 detik.']);
    exit;
}

// Lanjut proses agent...
$task   = json_decode(file_get_contents('php://input'), true)['task'] ?? '';
$result = $agent->run($task);
echo json_encode(['result' => $result, 'user' => $user['name']]);

Poin Kunci Bab 17

Authentication dan rate limiting bukan opsional — mereka adalah wajib sebelum deploy ke production. Tanpa ini, siapa pun bisa menggunakan agent Anda dan menguras kredit API Anda dalam hitungan menit.