Cara Melindungi API dengan API Key: Proteksi Endpoint dari Akses Tidak Dikenal
Admin
Penulis Artikel
Cara Melindungi API dengan API Key: Proteksi Endpoint dari Akses Tidak Dikenal
Di artikel sebelumnya, kita sudah menambahkan JWT authentication ke Todo List API — setiap user harus login dulu untuk mengakses datanya. Tapi JWT punya asumsi dasar: yang mengakses adalah manusia yang login lewat email & password.
Bagaimana kalau yang mengakses API kita bukan manusia, melainkan aplikasi atau servis lain? Misalnya, sekolahmu punya sistem dashboard terpisah yang ingin menampilkan ringkasan jumlah todo dari API kita, atau ada partner/vendor yang ingin mengintegrasikan sistem mereka dengan API kita secara otomatis. Servis semacam ini tidak punya “akun user” dan tidak bisa login — untuk kasus inilah API Key digunakan.
Artikel ini melanjutkan project Todo List API dari artikel pertama hingga keempat. Setelah ini, kita akan lanjut ke Validasi Input dan Pagination API.
1. API Key vs JWT: Dua Kartu yang Berbeda Fungsi
Analogi: bayangkan sekolahmu punya dua jenis kartu akses yang berbeda:
Kartu Pelajar (JWT) — dimiliki oleh individu (siswa/guru), didapat lewat proses login/verifikasi identitas, dan bisa menunjukkan siapa pemegangnya secara spesifik (nama, kelas, role). Kartu ini biasa dipakai untuk aktivitas personal seperti absen atau pinjam buku ke perpustakaan atas namamu sendiri.
Kartu Akses Vendor Kantin (API Key) — diberikan ke pihak luar (misalnya vendor katering) supaya mereka bisa keluar-masuk area dapur sekolah untuk mengantar bahan makanan. Kartu ini tidak mewakili “siapa” secara personal, tapi mewakili “aplikasi/pihak mana” yang diberi izin akses terbatas ke satu area tertentu saja.
Tabel perbandingannya:
Aspek | JWT (Authentication) | API Key |
|---|---|---|
Mewakili siapa | User individu yang login | Aplikasi/servis/klien tertentu |
Cara didapat | Login dengan email & password | Diberikan manual oleh admin/developer |
Masa berlaku | Biasanya pendek (jam/hari), bisa expired | Biasanya panjang, sampai sengaja dicabut |
Contoh pemakaian | User membuka aplikasi mobile, login, kelola data miliknya | Sistem lain mengambil data ringkasan secara otomatis (machine-to-machine) |
Info yang dibawa | Identitas user (id, role, dll) | Hanya identitas klien/aplikasi pemanggil |
Di project kita, API Key akan dipakai khusus untuk endpoint yang ditujukan untuk integrasi luar — bukan untuk menggantikan JWT di endpoint CRUD todo milik user.
2. Studi Kasus: Endpoint Statistik untuk Dashboard Eksternal
Anggap aplikasi todomu ingin membuat endpoint baru: GET /api/stats/summary, yang mengembalikan ringkasan jumlah total todo dan jumlah todo yang sudah selesai — dipakai oleh dashboard monitoring terpisah yang dikelola tim lain. Dashboard ini bukan “user” yang login, jadi tidak masuk akal kalau harus register/login dulu. Di sinilah API Key dipakai.
3. Generate API Key yang Aman
Analogi: membuat API Key itu seperti mencetak kartu akses vendor — kodenya harus acak dan panjang, supaya tidak bisa ditebak-tebak seperti menebak PIN ATM 4 digit. Jangan pernah pakai kata yang gampang ditebak seperti "apikey123".
Generate API key acak lewat terminal menggunakan modul bawaan Node.js:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
Contoh hasilnya (punyamu pasti berbeda, karena acak):
7f3a1c9e2b8d4f6a0c1e9b7d3f5a8c2e6b0d4f9a1c3e7b5d9f2a4c6e8b0d1f3a
Simpan hasilnya ke .env:
.env (tambahkan baris berikut)
EXTERNAL_API_KEY=7f3a1c9e2b8d4f6a0c1e9b7d3f5a8c2e6b0d4f9a1c3e7b5d9f2a4c6e8b0d1f3a
Update juga .env.example:
PORT=3000
MONGODB_URI=mongodb://127.0.0.1:27017/todo_api_db
NODE_ENV=development
JWT_SECRET=isi-dengan-secret-kamu-sendiri
JWT_EXPIRES_IN=1d
EXTERNAL_API_KEY=isi-dengan-api-key-kamu-sendiri
4. Membuat Middleware Pengecek API Key
Analogi: middleware ini seperti satpam khusus pintu belakang dapur sekolah — dia tidak peduli siapa nama vendornya, dia cuma mengecek satu hal: apakah kartu akses yang ditunjukkan cocok dengan kartu yang terdaftar di daftar vendor resmi sekolah. Kalau tidak cocok, pintu tidak akan dibuka sama sekali.
src/middlewares/apiKey.middleware.js
const AppError = require("../utils/AppError");
function checkApiKey(req, res, next) {
// API key dikirim lewat header custom: x-api-key
const apiKey = req.headers["x-api-key"];
if (!apiKey) {
return next(new AppError("API key is missing. Provide it via 'x-api-key' header", 401));
}
if (apiKey !== process.env.EXTERNAL_API_KEY) {
return next(new AppError("Invalid API key", 401));
}
next();
}
module.exports = checkApiKey;
Kenapa pakai header
x-api-key, bukanAuthorizationseperti JWT? Supaya jelas dibedakan secara konvensi:Authorization: Bearer <token>dipakai untuk autentikasi user (JWT), sedangkanx-api-keyadalah konvensi umum industri untuk mengirim API key milik aplikasi/klien.
5. Membuat Endpoint Statistik
Tambahkan function baru di service todo untuk menghitung statistik:
src/services/todo.service.js (tambahkan function ini sebelum module.exports)
async function getSummaryStats() {
const totalTodos = await Todo.countDocuments();
const completedTodos = await Todo.countDocuments({ completed: true });
const pendingTodos = totalTodos - completedTodos;
return { totalTodos, completedTodos, pendingTodos };
}
Jangan lupa tambahkan getSummaryStats ke dalam module.exports di file yang sama:
module.exports = {
createTodo,
getAllTodos,
getAllTodosForAdmin,
getTodoById,
updateTodo,
deleteTodo,
getSummaryStats,
};
Buat controller baru khusus untuk statistik:
src/controllers/stats.controller.js
const todoService = require("../services/todo.service");
const catchAsync = require("../utils/catchAsync");
const getSummary = catchAsync(async (req, res, next) => {
const stats = await todoService.getSummaryStats();
res.status(200).json({
success: true,
message: "Summary retrieved successfully",
data: stats,
});
});
module.exports = { getSummary };
Buat route baru khusus untuk stats, dilindungi checkApiKey (bukan protect milik JWT):
src/routes/stats.routes.js
const express = require("express");
const router = express.Router();
const statsController = require("../controllers/stats.controller");
const checkApiKey = require("../middlewares/apiKey.middleware");
router.get("/summary", checkApiKey, statsController.getSummary);
module.exports = router;
Daftarkan route baru ini di app.js:
src/app.js (update penuh)
const express = require("express");
const todoRoutes = require("./routes/todo.routes");
const authRoutes = require("./routes/auth.routes");
const statsRoutes = require("./routes/stats.routes");
const logger = require("./middlewares/logger.middleware");
const notFound = require("./middlewares/notFound.middleware");
const errorHandler = require("./middlewares/errorHandler.middleware");
const app = express();
app.use(logger);
app.use(express.json());
app.get("/", (req, res) => {
res.json({ message: "Todo API is running" });
});
app.use("/api/auth", authRoutes);
app.use("/api/todos", todoRoutes);
app.use("/api/stats", statsRoutes);
app.use(notFound);
app.use(errorHandler);
module.exports = app;
Perhatikan bahwa /api/stats/summary ini tidak memakai protect (middleware JWT) sama sekali — dia berdiri sendiri dengan proteksi checkApiKey. Ini contoh nyata bahwa satu API bisa punya dua jenis proteksi berbeda untuk kebutuhan berbeda, tergantung siapa/apa yang mengaksesnya.
6. Struktur Folder Setelah Artikel Ini
todo-api/
├── src/
│ ├── config/
│ │ └── db.js
│ ├── middlewares/
│ │ ├── logger.middleware.js
│ │ ├── notFound.middleware.js
│ │ ├── errorHandler.middleware.js
│ │ ├── auth.middleware.js
│ │ ├── restrictTo.middleware.js
│ │ └── apiKey.middleware.js
│ ├── models/
│ │ ├── todo.model.js
│ │ └── user.model.js
│ ├── services/
│ │ ├── todo.service.js
│ │ └── auth.service.js
│ ├── controllers/
│ │ ├── todo.controller.js
│ │ ├── auth.controller.js
│ │ └── stats.controller.js
│ ├── routes/
│ │ ├── todo.routes.js
│ │ ├── auth.routes.js
│ │ └── stats.routes.js
│ ├── utils/
│ │ ├── AppError.js
│ │ └── catchAsync.js
│ ├── app.js
│ └── server.js
├── .env
├── .env.example
├── .gitignore
└── package.json
7. Menguji Proteksi API Key
npm run dev
Tes 1 — Akses tanpa API key (harus ditolak).
curl http://localhost:3000/api/stats/summary
{
"success": false,
"message": "API key is missing. Provide it via 'x-api-key' header"
}
Tes 2 — Akses dengan API key yang salah (harus ditolak).
curl http://localhost:3000/api/stats/summary \
-H "x-api-key: kunci-ngasal"
{
"success": false,
"message": "Invalid API key"
}
Tes 3 — Akses dengan API key yang benar (harus berhasil). Ganti <API_KEY> dengan nilai EXTERNAL_API_KEY di .env:
curl http://localhost:3000/api/stats/summary \
-H "x-api-key: <API_KEY>"
{
"success": true,
"message": "Summary retrieved successfully",
"data": {
"totalTodos": 12,
"completedTodos": 5,
"pendingTodos": 7
}
}
Tes 4 — Buktikan endpoint ini tidak bisa diakses pakai JWT token (karena beda mekanisme).
curl http://localhost:3000/api/stats/summary \
-H "Authorization: Bearer <JWT_TOKEN>"
{
"success": false,
"message": "API key is missing. Provide it via 'x-api-key' header"
}
Ini membuktikan checkApiKey murni mengecek header x-api-key, sama sekali tidak peduli dengan header Authorization yang dipakai JWT.
8. Praktik Baik Mengelola API Key
Jangan pernah commit API key ke Git — sama seperti
JWT_SECRET, simpan hanya di.env.Beri satu API key per klien/partner, jangan pakai satu key yang sama untuk semua pihak eksternal. Kalau satu key bocor atau disalahgunakan, kamu bisa mencabut key itu saja tanpa mengganggu partner lain.
Batasi hak akses API key — API key idealnya hanya boleh mengakses endpoint tertentu yang memang ditujukan untuknya, seperti yang kita lakukan dengan memisahkan route
/api/statsdari route/api/todos.Rotasi API key secara berkala untuk sistem yang benar-benar production, terutama kalau ada indikasi key pernah bocor.
9. Perbandingan Sebelum dan Sesudah
Aspek | Sebelum | Sesudah |
|---|---|---|
Akses untuk servis eksternal | Belum ada mekanisme khusus | Tersedia lewat header x-api-key |
Endpoint statistik | Belum ada | GET /api/stats/summary, dilindungi API key |
Pemisahan jenis proteksi | Semua endpoint pakai JWT | Endpoint user pakai JWT, endpoint integrasi pakai API key |
10. Kesimpulan
Sekarang Todo List API kita punya dua mekanisme proteksi yang berjalan berdampingan sesuai kebutuhannya masing-masing: JWT untuk mengenali user individu yang login, dan API Key untuk mengizinkan akses machine-to-machine dari aplikasi/servis eksternal tanpa perlu konsep login. Memahami kapan memakai yang mana adalah skill penting saat kamu nanti terjun ke proyek nyata yang sering butuh integrasi dengan sistem lain.
Sejauh ini, endpoint kita menerima input apa saja tanpa validasi ketat, dan endpoint GET /api/todos akan mengembalikan semua data sekaligus tanpa batasan — ini akan jadi masalah kalau data sudah ribuan. Artikel selanjutnya akan membahas cara memvalidasi input dengan benar dan menambahkan pagination.
Lanjutkan ke artikel berikutnya: Validasi Input dan Pagination API: Best Practice REST API di Express
Artikel Terkait
Panduan Membuat Animasi Animated on Scroll di Website Anda
Pelajari cara membuat animasi menarik pada scroll di website Anda dengan tutorial ini. Tampilkan konten dengan cara yang lebih interaktif!
Cara Menangani Error dan HTTP Status Code yang Benar
cara menentukan HTTP status code yang benar untuk tiap jenis error, dan cara membuat custom Error
Tutorial Membuat REST API dengan Laravel yang Mudah dan Praktis
Pelajari langkah demi langkah cara membuat REST API menggunakan Laravel dengan mudah. Cocok untuk pemula dan pengembang berpengalaman.
