GRAPHQL SECURITY API-SECURITY OWASP WEB-SECURITY BACKEND-SECURITY APPLICATION-SECURITY DATA-SECURITY VULNERABILITY THREAT-PREVENTION BEST-PRACTICES API SYSTEM-DESIGN

GraphQL Security: Mengamankan API GraphQL Anda dari Ancaman Umum (OWASP API Top 10 untuk GraphQL)

⏱️ 10 menit baca
👨‍💻

GraphQL Security: Mengamankan API GraphQL Anda dari Ancaman Umum (OWASP API Top 10 untuk GraphQL)

1. Pendahuluan

GraphQL telah merevolusi cara kita membangun API, menawarkan fleksibilitas luar biasa bagi klien untuk meminta data persis yang mereka butuhkan. Ini adalah pedang bermata dua: kekuatan dan fleksibilitas ini, jika tidak dikelola dengan benar, dapat membuka pintu bagi berbagai celah keamanan yang unik dan berpotensi merusak.

Meskipun GraphQL sering kali dianggap lebih “aman” karena sifatnya yang eksplisit (klien hanya mendapatkan apa yang diminta), realitanya jauh lebih kompleks. Struktur GraphQL yang memungkinkan nested queries yang dalam, introspection, dan single endpoint yang serbaguna, memperkenalkan tantangan keamanan baru yang tidak selalu ada pada API REST tradisional.

OWASP (Open Web Application Security Project) telah merilis daftar “API Security Top 10” yang menjadi panduan standar untuk mengidentifikasi dan mitigasi risiko keamanan pada API secara umum. Namun, mengingat karakteristik unik GraphQL, komunitas keamanan juga telah mengadaptasi daftar ini secara spesifik untuk GraphQL.

Artikel ini akan membawa Anda menyelami ancaman keamanan paling umum pada API GraphQL, mengacu pada prinsip OWASP, dan memberikan strategi praktis serta contoh konkret untuk melindungi API GraphQL Anda. Mari kita pastikan kekuatan GraphQL tidak menjadi kelemahan terbesar Anda!

2. Ancaman 1: Excessive Data Exposure (Over-fetching Data)

📌 Masalah: Salah satu daya tarik GraphQL adalah kemampuannya untuk mengambil data yang spesifik. Namun, jika resolver Anda tidak dirancang dengan hati-hati, GraphQL bisa secara tidak sengaja mengekspos data sensitif yang seharusnya tidak dilihat oleh pengguna tertentu, meskipun data tersebut tidak diminta secara eksplisit oleh klien.

Contoh Skenario: Misalkan Anda memiliki schema User dengan field id, name, email, dan creditCardNumber. Seorang klien mungkin hanya meminta id dan name dari User.

query GetUserBasicInfo($id: ID!) {
  user(id: $id) {
    id
    name
  }
}

Tapi jika resolver user Anda mengambil seluruh objek User dari database dan kemudian hanya memfilter field yang diminta setelah data sensitif seperti creditCardNumber sudah diakses, maka ada risiko data tersebut terekspos dalam log server, cache, atau bahkan error messages jika terjadi masalah.

Solusi Praktis:Field-Level Authorization: Terapkan logika otorisasi pada setiap field sensitif dalam schema Anda. Ini memastikan bahwa field tersebut hanya di-resolve jika pengguna memiliki izin yang sesuai.

// Contoh pseudo-code untuk resolver
const resolvers = {
  User: {
    creditCardNumber: (parent, args, context) => {
      if (!context.user.isAdmin) { // Hanya admin yang boleh melihat
        return null;
      }
      return parent.creditCardNumber;
    },
  },
  Query: {
    user: (parent, { id }, context) => {
      // Pastikan otorisasi di tingkat objek juga
      const user = fetchUserFromDB(id);
      if (!user || user.id !== context.user.id && !context.user.isAdmin) {
        throw new Error("Unauthorized access");
      }
      return user;
    },
  },
};

Data Transfer Objects (DTOs) atau Projections: Gunakan DTOs atau fitur projection dari ORM/ODM Anda untuk mengambil data dari database hanya field yang benar-benar dibutuhkan oleh resolver atau yang diizinkan untuk diakses. Ini mengurangi risiko data sensitif berada di memori server atau log tanpa perlu.

3. Ancaman 2: Broken Function Level Authorization (A5:2023)

📌 Masalah: Mirip dengan API REST, GraphQL juga rentan terhadap masalah otorisasi di mana pengguna dapat mengakses fungsi atau resource yang seharusnya tidak mereka miliki. Karena GraphQL menggunakan single endpoint, otorisasi berbasis path yang umum di REST tidak berlaku. Otorisasi harus diterapkan pada tingkat resolver atau field.

Contoh Skenario: Seorang pengguna biasa (non-admin) dapat memanggil mutation deleteUser(id: 123) yang seharusnya hanya dapat diakses oleh administrator.

Solusi Praktis:Context-Based Authorization di Resolver: Lakukan pemeriksaan izin di awal setiap resolver yang memerlukan otorisasi. context di GraphQL adalah tempat yang tepat untuk menyimpan informasi pengguna yang terautentikasi dan perannya.

const resolvers = {
  Mutation: {
    deleteUser: (parent, { id }, context) => {
      if (!context.user || !context.user.isAdmin) {
        throw new Error("Forbidden: Only administrators can delete users.");
      }
      // Lanjutkan logika penghapusan user
      return deleteUserFromDB(id);
    },
  },
};

Custom Directives: Banyak implementasi GraphQL (misalnya Apollo Server, graphql-js) mendukung custom directives. Anda bisa membuat directive @auth atau @hasRole untuk menerapkan otorisasi secara deklaratif pada field atau type dalam schema Anda.

type User @auth(requires: ADMIN) {
  id: ID!
  name: String!
  email: String! @auth(requires: ADMIN) # Contoh field-level auth
}

type Mutation {
  deleteUser(id: ID!): Boolean @auth(requires: ADMIN)
}

4. Ancaman 3: Broken Object Level Authorization (A4:2023)

📌 Masalah: Ini adalah ancaman yang sangat umum dan berbahaya, sering disebut IDOR (Insecure Direct Object References). Pengguna dapat memanipulasi parameter query atau mutation (misalnya id objek) untuk mengakses resource yang dimiliki oleh pengguna lain tanpa otorisasi yang benar.

Contoh Skenario: Seorang pengguna A mencoba mengambil detail pesanan dengan ID order(id: "ORDER_456"). Jika tidak ada validasi kepemilikan, pengguna A bisa saja mengubah ID menjadi ORDER_123 (milik pengguna B) dan mendapatkan akses ke data pesanan pengguna B.

Solusi Praktis:Validasi Kepemilikan Objek di Resolver: Ini adalah strategi pertahanan paling penting. Setiap kali Anda mengambil resource berdasarkan ID yang diberikan oleh klien, selalu validasi bahwa resource tersebut benar-benar milik pengguna yang sedang terautentikasi (atau pengguna memiliki izin untuk mengaksesnya).

const resolvers = {
  Query: {
    order: (parent, { id }, context) => {
      const order = fetchOrderFromDB(id);
      if (!order) {
        throw new Error("Order not found.");
      }
      // PENTING: Validasi kepemilikan!
      if (order.userId !== context.user.id && !context.user.isAdmin) {
        throw new Error("Unauthorized access to this order.");
      }
      return order;
    },
  },
};

Gunakan Global ID yang Di-obfuscate: Daripada menggunakan ID database internal yang mudah ditebak (misalnya 1, 2, 3), gunakan Global ID yang di-obfuscate atau di-encode (misalnya Base64) seperti yang dipopulerkan oleh Relay. Ini tidak sepenuhnya mencegah IDOR, tetapi mempersulit penyerang untuk menebak ID lain.

5. Ancaman 4: Resource Exhaustion (A6:2023)

📌 Masalah: Fleksibilitas GraphQL memungkinkan klien untuk meminta nested queries yang sangat dalam atau meminta banyak field pada saat yang bersamaan. Ini dapat menyebabkan resource exhaustion (kehabisan sumber daya) pada server, seperti penggunaan CPU atau memori yang tinggi, dan bahkan serangan Denial-of-Service (DoS).

Contoh Skenario: Seorang penyerang dapat mengirim query seperti ini:

query DeepQuery {
  user(id: "1") {
    friends {
      friends {
        friends {
          friends {
            # ... bisa sampai puluhan atau ratusan level
            name
          }
        }
      }
    }
  }
}

Query ini, jika tidak dibatasi, bisa memicu banyak panggilan database atau komputasi yang mahal, melumpuhkan server Anda.

Solusi Praktis:Query Depth Limiting: Batasi seberapa dalam sebuah query dapat bersarang. Jika query melebihi batas kedalaman yang ditentukan, tolak query tersebut.

// Contoh pseudo-code untuk implementasi depth limiting
import { depthLimit } from 'graphql-depth-limit';

const server = new ApolloServer({
  schema,
  validationRules: [depthLimit(7)], // Batasi hingga 7 level kedalaman
});

Query Complexity Analysis: Berikan “biaya” atau “poin” pada setiap field dalam schema Anda (misalnya, mengambil daftar 100 item lebih mahal daripada satu item). Kemudian, hitung total biaya query yang masuk. Tolak query jika biayanya melebihi ambang batas yang diizinkan.

// Contoh pseudo-code untuk complexity analysis
import { createComplexityRule, simpleEstimator } from 'graphql-query-complexity';

const ComplexityRule = createComplexityRule({
  estimators: [
    simpleEstimator({ defaultComplexity: 1 }), // Default complexity untuk setiap field
    // Tambahkan estimator kustom untuk field yang lebih kompleks, misal list dengan argument limit
    ({ fieldName, args, childComplexity }) => {
      if (fieldName === 'friends' && args.limit) {
        return args.limit * childComplexity;
      }
      return 0; // Tidak menambah complexity jika tidak cocok
    },
  ],
  maximumComplexity: 1000, // Batas total complexity
  onComplete: (complexity) => {
    console.log('Query Complexity:', complexity);
  },
});

const server = new ApolloServer({
  schema,
  validationRules: [ComplexityRule],
});

Rate Limiting: Terapkan rate limiting pada endpoint GraphQL Anda. Ini membatasi jumlah request yang dapat dibuat oleh klien dalam jangka waktu tertentu, mencegah penyerang membanjiri server dengan query yang mahal.

6. Best Practices Tambahan untuk Keamanan GraphQL

💡 Selain ancaman spesifik di atas, ada beberapa praktik terbaik umum yang harus Anda terapkan untuk memperkuat keamanan API GraphQL Anda:

Kesimpulan

GraphQL menawarkan kekuatan dan fleksibilitas yang luar biasa, tetapi dengan kekuatan besar datang pula tanggung jawab besar. Keamanan API GraphQL bukanlah sesuatu yang bisa diabaikan. Dengan memahami ancaman spes