Episode 15 mengamankan GraphQL API dari ancaman yang umum: query depth dan complexity attacks, introspection abuse, rate limiting berbasis request dan cost, CORS yang benar, HTTPS dan secure headers, hingga Automatic Persisted Queries dengan whitelist.

GraphQL menghadirkan kekuatan luar biasa — dan kekuatan itu bisa disalahgunakan. Sebuah query yang tampak polos bisa membebani server ratusan kali lipat query normal. Episode 15 mengamankan API GraphQL kalian dari ancaman yang paling umum terjadi.
Kita akan membahas query depth limiting, query complexity analysis, rate limiting, introspetri, konfigurasi CORS, HTTPS dan secure headers, serta persisted queries dengan whitelist — semua merupakan bagian dari checklist keamanan yang wajib diterapkan sebelum production.
Ancaman paling khas GraphQL adalah deep nesting. Query user { posts { comments { user { posts { comments ... } } } } } bisa membuat resolver berjalan jutaan kali. Server menghabiskan resource untuk query yang tidak wajar, membuka pintu Denial of Service (DOS).
Ancaman lain: batch attacks yang mengirim banyak operasi dalam satu request, penyalahgunaan introspection untuk memetakan schema, dan query yang meminta list tanpa batas. Ketiganya perlu ditangani dengan strategi berbeda — dimulai dari pembatasan query.
Pembatasan kedalaman membatasi seberapa dalam query bisa bersarang; install library-nya lewat npm install graphql-depth-limit:
npm install graphql-depth-limitimport depthLimit from "graphql-depth-limit";
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [depthLimit(10)],
});Query yang melewati kedalaman 10 ditolak pada tahap validasi — bahkan sebelum resolver berjalan. Nilai yang dipilih perlu diuji: terlalu kecil akan menolak query legit (misalnya dashboard yang dalam), terlalu besar akan kehilangan proteksi. Mulai dari 8-12 untuk sebagian besar aplikasi.
Depth limit tidak menghitung berapa banyak resolver yang benar-benar dieksekusi. Query complexity analysis melangkah lebih jauh: setiap field diberi cost, dan total cost query dibatasi.
npm install graphql-cost-analysisimport costAnalysis from "graphql-cost-analysis";
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [
costAnalysis({
maximumCost: 1000,
defaultCost: 1,
costMap: {
User: { posts: { multiply: 5 } },
},
}),
],
});Field yang mahal (misalnya list besar atau panggilan eksternal) diberi cost lebih tinggi lewat costMap. Total query dibandingkan dengan maximumCost; yang melewati batas ditolak. Pendekatan ini menggagalkan query yang murah "per field" tapi mahal "secara kumulatif".
Rate limiting mencegah satu client membebani server dalam jendela waktu. Untuk GraphQL ada dua pendekatan:
import { createRateLimitDirective } from "graphql-rate-limit";
const rateLimitDirective = createRateLimitDirective({
keyGenerator: () => "global",
max: 100,
window: "1m",
message: "Terlalu banyak request, coba lagi nanti",
});Terapkan rate limit per user menggunakan identitas dari ctx.user.id, dan pertimbangkan whitelist untuk API key service-to-service. Kombinasikan request-based dan cost-based untuk perlindungan berlapis.
Introspection memungkinkan client dan tooling membaca seluruh schema — berguna di development, tapi memberi peta bagi penyerang di production:
const server = new ApolloServer({
typeDefs,
resolvers,
introspection: process.env.NODE_ENV !== "production",
validationRules: [
...(process.env.NODE_ENV === "production"
? [depthLimit(10), costAnalysis(costOptions)]
: []),
],
});Ingat: ini "security by obscurity" dan bukan pengganti authorization (episode 14) — field yang sensitif tetap harus dilindungi langsung. Beberapa tim justru membiarkan introspection aktif untuk API publik dan mengandalkan proteksi lain.
Konfigurasi CORS dengan whitelist origin spesifik, jangan pakai * untuk API dengan credentials:
import cors from "cors";
app.use(cors({
origin: ["https://app.kalian.com", "https://admin.kalian.com"],
credentials: true,
}));Selalu sajikan API di atas HTTPS dengan TLS modern, dan tambahkan secure headers seperti Strict-Transport-Security. Di balik CDN atau load balancer, pastikan proxy meneruskan header yang dibutuhkan.
Persisted queries memisahkan query dari request: client mengirim hash query, server memetakan hash ke query yang sudah disetujui. Manfaatnya dua: bandwidth lebih kecil dan whitelist keamanan.
const server = new ApolloServer({
typeDefs,
resolvers,
persistedQueries: { ttl: 300 },
});Untuk whitelist ketat, daftarkan query yang disetujui secara eksplisit dan tolak yang tidak dikenal — ini juga mematikan query arbitrer yang sering dipakai penyerang. APQ akan kembali dibahas dari sisi performa dan caching di episode 19.
Inti yang harus dibawa pulang:
graphql-depth-limit menolak query terlalu dalam; cost analysis membatasi biaya total query.Di episode 16 selanjutnya kalian akan mempelajari subscriptions untuk fitur real-time — konsep subscriptions, transport WebSocket dengan graphql-ws, setup server Apollo 4, PubSub pattern, PubSub berbasis Redis untuk production, keamanan subscriptions, hingga integrasi client dengan Apollo Client. API kalian akan mulai "berbicara" secara real-time!