Cara Membuat API Key Generator di PHP

setiap kali membangun layanan backend atau REST API, mekanisme otentikasi adalah dinding pertahanan pertama yang wajib disiapkan. waktu pertama kali membuat web service bertahun-tahun lalu, saya sempat melakukan kesalahan fatal yang sering dilakukan pemula: menyimpan API key sebagai teks biasa (plain text) di tabel database ๐
bayangkan jika database itu bocor atau diakses oleh pihak luar, semua akses layanan klien langsung jatuh ke tangan orang lain tanpa perlindungan. dari situ saya belajar aturan emas keamanan API: perlakukan API key persis seperti password. kunci teks asli hanya diperlihatkan satu kali ke pengguna saat dibuat, sementara yang disimpan di database adalah nilai acak yang sudah di-hash.
di artikel ini kita pelajari cara membuat sistem API key generator profesional di PHP: mulai dari menghasilkan kunci acak yang aman secara kriptografi, desain tabel database, validasi header Authorization: Bearer, pencegahan timing attack, sampai trik mengatasi bug klasik Apache yang menelan auth header.
sebelum melangkah lebih jauh, jika kalian ingin memahami dasar pertukaran data JSON di REST API, silakan baca ulasan saya di membuat rest api php sederhana.
1. Alur Kerja API Key yang Aman
sebelum menulis kode, penting memahami alur siklus hidup API key:
[Klien Minta Key] โ [PHP Generate 32 bytes acak] โ [Hash SHA-256]
โ โ
[Tampilkan Kunci Asli ke User] [Simpan Hash ke DB]
saat klien memanggil endpoint API di masa depan:
- Klien mengirimkan kunci asli di header HTTP:
Authorization: Bearer pk_live_xxx - Server PHP mengambil kunci tersebut, lalu menghitung nilai hash SHA-256 dari kunci tersebut
- Server mencari nilai hash di database menggunakan prepared statement
- Jika cocok, request diizinkan lanjut. jika tidak, server mengembalikan status
401 Unauthorized
dengan sistem ini, andai database kalian suatu saat dicuri orang, penyerang hanya mendapatkan kumpulan string hash yang tidak bisa dipakai langsung untuk memanggil API kalian.
2. Mengapa Wajib Memakai random_bytes()?
di PHP ada beberapa cara menghasilkan string acak, tetapi hanya satu yang aman untuk keamanan:
// โ SANGAT BERBAHAYA: mudah ditebak dan diprediksi
$keyBuruk1 = md5(time());
$keyBuruk2 = substr(str_shuffle("0123456789abcdef"), 0, 32);
$keyBuruk3 = uniqid("key_", true);
// โ
STANDAR KRIPTOGRAFI TINGGI: CSPRNG aman
$bytesAman = random_bytes(32); // menghasilkan 32 byte entropi biner
$keyAman = bin2hex($bytesAman); // diubah menjadi 64 karakter heksadesimal
random_bytes() adalah fungsi CSPRNG (Cryptographically Secure Pseudo-Random Number Generator) bawaan PHP. fungsi ini mengambil entropi acak dari kernel sistem operasi (seperti /dev/urandom di Linux atau CryptGenRandom di Windows), sehingga nilainya acak murni dan mustahil ditebak penyerang.
3. Desain Tabel Database MySQL
kita membutuhkan tabel untuk menyimpan hash API key beserta relasi pemiliknya. buat tabel berikut di database MySQL kalian:
CREATE TABLE `api_keys` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`user_id` INT NOT NULL,
`name` VARCHAR(100) NOT NULL COMMENT 'Label nama aplikasi klien',
`api_key_hash` VARCHAR(64) NOT NULL UNIQUE COMMENT 'Hash SHA-256 dari key asli',
`key_prefix` VARCHAR(16) NOT NULL COMMENT 'Contoh: pk_live_abcd untuk display',
`status` ENUM('active', 'revoked') DEFAULT 'active',
`last_used_at` DATETIME NULL,
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX `idx_hash` (`api_key_hash`),
INDEX `idx_user` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
perhatikan kolom key_prefix. karena kita tidak menyimpan kunci asli, kita menyimpan 8-10 karakter depan kunci tersebut agar di antarmuka dashboard pengguna bisa melihat petunjuk kunci mana yang sedang aktif, misalnya: pk_live_8f3a...****.
4. Fungsi Pembuat Kunci (Generator)
berikut kode lengkap untuk membuat kunci baru, menghitung hash-nya, dan menyimpannya ke database:
<?php
// koneksi.php
$db = new mysqli("localhost", "root", "", "db_api");
if ($db->connect_error) {
die("Koneksi gagal: " . $db->connect_error);
}
function generateApiKey(int $userId, string $namaAplikasi, mysqli $conn): array
{
// 1. Buat token acak 32 bytes (64 karakter hex)
$tokenAcak = bin2hex(random_bytes(32));
// 2. Beri prefix standar industri agar mudah dikenali
$plainApiKey = "pk_live_" . $tokenAcak;
// 3. Hash kunci asli menggunakan algoritma SHA-256
$hashKey = hash('sha256', $plainApiKey);
// 4. Ambil prefix untuk tanda pengenal di dashboard
$prefixDisplay = substr($plainApiKey, 0, 16);
// 5. Simpan hash ke database menggunakan Prepared Statement
$stmt = $conn->prepare(
"INSERT INTO api_keys (user_id, name, api_key_hash, key_prefix) VALUES (?, ?, ?, ?)"
);
$stmt->bind_param("isss", $userId, $namaAplikasi, $hashKey, $prefixDisplay);
$stmt->execute();
$stmt->close();
// 6. Kembalikan kunci mentah ke pengguna (HANYA SEKALI INI!)
return [
'nama' => $namaAplikasi,
'api_key' => $plainApiKey,
'perhatian' => 'Salin kunci ini sekarang. Kunci tidak akan pernah ditampilkan lagi demi alasan keamanan.'
];
}
// Contoh pemakaian saat user klik tombol "Buat API Key" di dashboard
$hasil = generateApiKey(1, "Aplikasi Mobile Android", $db);
header('Content-Type: application/json');
echo json_encode($hasil, JSON_PRETTY_PRINT);
?>
setelah baris ini dieksekusi, nilai $plainApiKey yang tersimpan di memori akan hilang. jika pengguna lupa atau kehilangan kuncinya, jalan satu-satunya adalah mencabut (revoke) kunci lama dan membuat yang baru. pola ini persis seperti yang digunakan Stripe, OpenAI, dan Google Cloud.
5. Validasi API Key pada Endpoint API
sekarang kita buat modul penjaga (middleware sederhana) untuk memeriksa setiap request yang masuk ke endpoint API:
<?php
// middleware_api.php
function validasiAksesApi(mysqli $conn): array
{
// 1. Ambil header Authorization dari request HTTP
$authHeader = $_SERVER['HTTP_AUTHORIZATION']
?? $_SERVER['REDIRECT_HTTP_AUTHORIZATION']
?? '';
// Jika di lingkungan Apache lokal kadang tersimpan di getallheaders()
if (empty($authHeader) && function_exists('getallheaders')) {
$headers = getallheaders();
$authHeader = $headers['Authorization'] ?? $headers['authorization'] ?? '';
}
// 2. Pastikan formatnya sesuai standar: Bearer <token>
if (!preg_match('/^Bearer\s+(\S+)$/i', $authHeader, $matches)) {
http_response_code(401);
echo json_encode([
'status' => false,
'message' => 'Format otentikasi salah. Gunakan header Authorization: Bearer <API_KEY>'
]);
exit;
}
$rawApiKey = $matches[1];
// 3. Hash API key yang dikirim oleh klien
$incomingHash = hash('sha256', $rawApiKey);
// 4. Cari hash di database dengan status aktif
$stmt = $conn->prepare(
"SELECT id, user_id, name, status FROM api_keys WHERE api_key_hash = ? AND status = 'active' LIMIT 1"
);
$stmt->bind_param("s", $incomingHash);
$stmt->execute();
$result = $stmt->get_result();
$keyData = $result->fetch_assoc();
$stmt->close();
if (!$keyData) {
http_response_code(401);
echo json_encode([
'status' => false,
'message' => 'API Key tidak valid atau telah dinonaktifkan.'
]);
exit;
}
// 5. Update waktu pemakaian terakhir secara asynchronous/background
$updateStmt = $conn->prepare("UPDATE api_keys SET last_used_at = NOW() WHERE id = ?");
$updateStmt->bind_param("i", $keyData['id']);
$updateStmt->execute();
$updateStmt->close();
return $keyData;
}
?>
Cara Memakainya di Endpoint Produk:
<?php
// api/produk.php
require_once "../koneksi.php";
require_once "../middleware_api.php";
// Jalankan validasi, jika gagal skrip otomatis berhenti di kode 401
$klien = validasiAksesApi($db);
// Jika lolos, jalankan logika endpoint biasa
header('Content-Type: application/json');
echo json_encode([
'status' => true,
'message' => 'Selamat datang di API Produk',
'klien' => $klien['name'],
'data' => [
['id' => 1, 'nama' => 'Monitor Gaming 24 Inch', 'harga' => 1850000],
['id' => 2, 'nama' => 'Mouse Wireless Ergonomic', 'harga' => 320000]
]
]);
?>
6. Solusi Masalah Klasik: Apache Menelan Auth Header
ini masalah nomor satu yang paling sering membingungkan programmer PHP pemula saat menguji API di server lokal (XAMPP/Laragon) atau cPanel:
kodingan sudah benar dan Postman sudah mengirim header
Authorization: Bearer pk_live_xxx, tetapi$_SERVER['HTTP_AUTHORIZATION']di PHP selalu bernilai kosong!
penyebabnya: secara bawaan, modul Apache mod_rewrite tidak meneruskan header otentikasi ke skrip PHP berbasis CGI/FastCGI.
solusinya sangat sederhana. buka file .htaccess di folder root project kalian, lalu tambahkan aturan berikut di bagian IfModule mod_rewrite.c:
<IfModule mod_rewrite.c>
RewriteEngine On
# Meneruskan Authorization header ke variabel PHP
RewriteCond %{HTTP:Authorization} ^(.*)
RewriteRule .* - [e=HTTP_AUTHORIZATION:%1]
</IfModule>
simpan file .htaccess tersebut. seketika PHP akan bisa membaca header Authorization secara normal.
7. Mencegah Serangan Timing Attack dengan hash_equals()
saat kalian membandingkan dua string rahasia di PHP, hindari operator kesetaraan biasa (===).
operator === berhenti memeriksa begitu menemukan karakter pertama yang berbeda. artinya, semakin banyak karakter depan yang cocok, semakin lama waktu yang dibutuhkan komputer untuk merespons (dalam fraksi mikrodetik). penyerang canggih bisa mengukur variasi waktu respons ini untuk menebak karakter satu per satu (timing attack).
solusinya gunakan fungsi bawaan PHP hash_equals():
// โ Rawan timing attack jika membandingkan token langsung
if ($tokenInput === $tokenDatabase) { ... }
// โ
Aman dari timing attack: waktu eksekusi konstan
if (hash_equals($hashDatabase, $hashInput)) {
// Kunci cocok sempurna
}
8. Rate Limiting Sederhana Berbasis Waktu
agar server kalian tidak tumbang akibat spam request atau perulangan script klien yang macet, kalian bisa membatasi kuota panggilan (misalnya maksimal 60 request per menit per key) menggunakan tabel log sederhana:
function cekRateLimit(int $apiKeyId, int $maksimalPerMenit, mysqli $conn): bool
{
// Catat panggilan saat ini
$insert = $conn->prepare("INSERT INTO api_logs (api_key_id, hit_at) VALUES (?, NOW())");
$insert->bind_param("i", $apiKeyId);
$insert->execute();
$insert->close();
// Hitung jumlah panggilan dalam 60 detik terakhir
$stmt = $conn->prepare(
"SELECT COUNT(*) AS total FROM api_logs
WHERE api_key_id = ? AND hit_at >= (NOW() - INTERVAL 1 MINUTE)"
);
$stmt->bind_param("i", $apiKeyId);
$stmt->execute();
$total = $stmt->get_result()->fetch_assoc()['total'];
$stmt->close();
return $total <= $maksimalPerMenit;
}
jika fungsi mengembalikan nilai false, segera kirim response status 429 Too Many Requests beserta header Retry-After: 60.
Kesimpulan
membuat sistem API key generator yang aman di PHP bukanlah perkara sulit asalkan mengikuti standar industri yang benar: gunakan random_bytes() untuk menghasilkan token berkualitas kriptografi tinggi, tambahkan prefix seperti pk_live_ untuk kemudahan pelacakan, dan selalu simpan versi hash SHA-256 di database dengan prepared statement.
jangan lupa pasang konfigurasi .htaccess untuk memastikan server Apache tidak membuang header otentikasi, serta terapkan hash_equals() dan rate limiting untuk perlindungan berlapis. setelah sistem otentikasi ini siap, kalian bisa membaca tutorial dasar manipulasi database di membuat crud php mysqli atau memperluas wawasan arsitektur endpoint di belajar rest api php.
Baca Juga Mengenai :
Pertanyaan yang Sering Diajukan
Mengapa API key tidak boleh disimpan sebagai teks biasa (plain text)?
Karena jika database kalian bocor atau diakses pihak internal yang tidak berhak, semua kunci API klien langsung terekspos dan bisa disalahgunakan. Simpanlah hasil hash (misalnya SHA-256) di database, dan hanya perlihatkan kunci asli sekali saja saat pengguna pertama kali membuatnya.
Kenapa harus memakai random_bytes dan bukan rand atau md5(uniqid())?
rand() dan uniqid() adalah pseudo-random generator yang polanya mudah ditebak oleh penyerang. random_bytes() adalah CSPRNG (Cryptographically Secure Pseudo-Random Number Generator) bawaan PHP yang mengambil entropi dari sistem operasi, sehingga mustahil ditebak.
Kenapa $_SERVER HTTP_AUTHORIZATION sering bernilai kosong di Apache?
Secara bawaan modul Apache PHP-FPM sering membuang header Authorization demi alasan keamanan legacy. Solusinya tambahkan baris RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] di file .htaccess agar header otentikasi diteruskan ke script PHP.
Apa fungsi prefix seperti pk_live_ pada API key?
Prefix memudahkan identifikasi visual jenis key (misal test vs live) dan memungkinkan pemindai keamanan otomatis (seperti GitHub Secret Scanning) mendeteksi jika ada programmer yang tidak sengaja mengunggah kunci rahasia ke repository publik.
Bagaimana cara mencegah brute-force pada API key?
Gabungkan dengan mekanisme rate limiting di database atau cache Redis untuk membatasi jumlah panggilan per menit, dan gunakan fungsi hash_equals() saat membandingkan string hash untuk mencegah serangan timing attack.
Kapan sebaiknya memakai API Key dibanding JWT (JSON Web Token)?
API key sangat cocok untuk komunikasi antar server (machine-to-machine) atau integrasi developer pihak ketiga jangka panjang. JWT lebih ideal untuk sesi login pengguna di aplikasi web atau mobile yang membutuhkan masa kedaluwarsa singkat dan data payload dinamis.

