Mengatasi CORS Error di API Gateway: Konfigurasi Console dan Header Lambda yang Wajib Ada

Frontend kamu tiba-tiba melempar error CORS policy: No 'Access-Control-Allow-Origin' header is present — padahal endpoint API Gateway-nya sudah benar dan bisa dipanggil dari curl tanpa masalah. Ini adalah salah satu jebakan paling umum saat membangun aplikasi serverless: mengaktifkan CORS di console API Gateway saja tidak cukup jika Lambda response-mu tidak menyertakan header yang tepat.

TL;DR — Ringkasan Cepat CORS di API Gateway

Komponen Yang Harus Dilakukan Kenapa Penting
API Gateway Console Aktifkan CORS per resource, deploy ulang stage Membuat API Gateway menangani preflight OPTIONS request
Lambda Response Sertakan Access-Control-Allow-Origin di setiap response Browser memvalidasi header ini di setiap actual request
Integration Type Lambda Proxy vs Non-Proxy menentukan siapa yang mengontrol header Salah pilih integrasi = header CORS tidak pernah sampai ke browser
Deploy Stage Wajib deploy ulang setelah perubahan apapun Perubahan di API Gateway tidak aktif tanpa deployment

Bagaimana CORS Bekerja di API Gateway

CORS (Cross-Origin Resource Sharing) adalah mekanisme browser — bukan mekanisme server. Browser yang menerapkan aturan ini, bukan API Gateway atau Lambda. Ketika frontend di https://app.example.com memanggil API di domain berbeda, browser melakukan dua hal:

  1. Preflight request — Browser mengirim HTTP OPTIONS request ke endpoint yang sama, menanyakan apakah server mengizinkan cross-origin request dari origin tersebut.
  2. Actual request — Jika server merespons preflight dengan header yang benar, browser baru mengirim request asli (GET, POST, dll.).

API Gateway perlu menangani keduanya. Preflight ditangani oleh konfigurasi CORS di API Gateway itu sendiri. Actual request ditangani oleh Lambda — dan Lambda wajib mengembalikan header CORS di response-nya, karena browser memvalidasinya lagi di sini.

sequenceDiagram participant B as Browser participant AG as API Gateway participant L as Lambda B->>AG: OPTIONS /users (Preflight) AG-->>B: 200 OK + CORS Headers Note over B,AG: Preflight selesai, browser lanjut B->>AG: POST /users (Actual Request) AG->>L: Forward request ke Lambda L-->>AG: Response + CORS Headers AG-->>B: Response diteruskan ke browser Note over B,L: Browser validasi CORS header
di actual response
  1. Preflight (OPTIONS) — Browser mengirim OPTIONS request sebelum actual request. API Gateway menangani ini langsung jika CORS dikonfigurasi.
  2. Preflight Response — API Gateway mengembalikan header Access-Control-Allow-Origin, Access-Control-Allow-Methods, dan Access-Control-Allow-Headers.
  3. Actual Request — Browser mengirim request asli (misalnya POST). Lambda memproses dan wajib menyertakan header CORS di response.
  4. Browser Validation — Browser memvalidasi header CORS di actual response. Jika tidak ada, error CORS muncul meskipun preflight berhasil.
Analoginya seperti masuk gedung perkantoran: security di lobi (preflight) memberi izin masuk, tapi setiap ruangan (actual response dari Lambda) tetap harus menunjukkan badge akses. Lolos security lobi tidak otomatis membuka semua pintu.

Perbedaan Lambda Proxy vs Non-Proxy Integration — Ini yang Sering Bikin Bingung

Sebelum masuk ke langkah konfigurasi, pahami dulu ini karena menentukan di mana kamu harus menambahkan header CORS:

  • Lambda Proxy Integration — API Gateway meneruskan request mentah ke Lambda dan mengembalikan response Lambda langsung ke client. Artinya, Lambda harus menyertakan header CORS di response-nya sendiri. API Gateway tidak bisa menambahkan header di sini.
  • Lambda Non-Proxy Integration — API Gateway mengontrol transformasi request dan response. Header CORS bisa dikonfigurasi di level API Gateway melalui Integration Response dan Method Response.

Mayoritas implementasi modern menggunakan Lambda Proxy Integration karena lebih sederhana. Jika kamu menggunakan ini, mengaktifkan CORS di console hanya menyelesaikan masalah preflight — Lambda tetap harus mengembalikan header CORS di setiap response.

Langkah 1: Aktifkan CORS di API Gateway Console (REST API)

Langkah ini menangani preflight OPTIONS request. Tanpa ini, browser bahkan tidak akan mencoba mengirim actual request.

  1. Buka API Gateway Console, pilih REST API yang kamu gunakan.
  2. Di panel kiri, pilih Resources, lalu klik resource yang ingin dikonfigurasi (misalnya /users).
  3. Klik menu Actions, pilih Enable CORS.
  4. Isi field berikut:
    • Access-Control-Allow-Origin — Masukkan origin spesifik seperti https://app.example.com. Gunakan * hanya untuk development karena tidak bisa digunakan bersama credentials.
    • Access-Control-Allow-Headers — Minimal: Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token
    • Access-Control-Allow-Methods — Pilih method yang relevan: GET,POST,PUT,DELETE,OPTIONS
  5. Klik Enable CORS and replace existing CORS headers.
  6. Deploy API — Klik Actions → Deploy API → pilih stage. Tanpa langkah ini, tidak ada perubahan yang aktif.

Verifikasi bahwa OPTIONS method sudah terbentuk dengan CLI:

aws apigateway get-method \
  --rest-api-id YOUR_API_ID \
  --resource-id YOUR_RESOURCE_ID \
  --http-method OPTIONS \
  --region us-east-1

Jika output menampilkan methodResponses dengan status 200 dan header Access-Control-Allow-Origin, konfigurasi preflight sudah benar.

Langkah 2: Tambahkan Header CORS di Lambda Response

Ini adalah bagian yang paling sering terlewat. Mengaktifkan CORS di console hanya menyelesaikan setengah masalah — browser tetap memvalidasi header CORS di actual response yang dikembalikan Lambda. Jika Lambda Proxy Integration yang kamu gunakan, Lambda harus mengembalikan header ini secara eksplisit.

Contoh response Lambda yang benar (Node.js):

🔽 Klik untuk melihat contoh Lambda response handler (Node.js)
exports.handler = async (event) => {
  const allowedOrigins = ['https://app.example.com'];
  const requestOrigin = event.headers && event.headers.origin;
  const allowOrigin = allowedOrigins.includes(requestOrigin)
    ? requestOrigin
    : allowedOrigins[0];

  const response = {
    statusCode: 200,
    headers: {
      'Access-Control-Allow-Origin': allowOrigin,
      'Access-Control-Allow-Headers': 'Content-Type,Authorization',
      'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE,OPTIONS',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ message: 'Success' })
  };

  return response;
};

Contoh yang sama untuk Python:

🔽 Klik untuk melihat contoh Lambda response handler (Python)
import json

def lambda_handler(event, context):
    allowed_origins = ['https://app.example.com']
    request_origin = (event.get('headers') or {}).get('origin', '')
    allow_origin = request_origin if request_origin in allowed_origins else allowed_origins[0]

    return {
        'statusCode': 200,
        'headers': {
            'Access-Control-Allow-Origin': allow_origin,
            'Access-Control-Allow-Headers': 'Content-Type,Authorization',
            'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE,OPTIONS',
            'Content-Type': 'application/json'
        },
        'body': json.dumps({'message': 'Success'})
    }

Perhatikan pola validasi origin di atas — alih-alih hardcode *, kode memeriksa apakah origin request ada di whitelist. Ini penting jika kamu menggunakan cookies atau Authorization header, karena Access-Control-Allow-Origin: * tidak bisa dikombinasikan dengan Access-Control-Allow-Credentials: true.

Langkah 3: Konfigurasi CORS untuk HTTP API (API Gateway v2)

Jika kamu menggunakan HTTP API (bukan REST API), mekanismenya berbeda. HTTP API memiliki konfigurasi CORS terpusat yang diterapkan di level API, bukan per resource.

aws apigatewayv2 update-api \
  --api-id YOUR_HTTP_API_ID \
  --cors-configuration AllowOrigins='https://app.example.com',AllowMethods='GET,POST,PUT,DELETE',AllowHeaders='Content-Type,Authorization' \
  --region us-east-1

Untuk HTTP API dengan Lambda Proxy Integration, Lambda tetap harus mengembalikan header CORS di response-nya jika kamu ingin kontrol granular per endpoint. Konfigurasi CORS di level API Gateway v2 menangani preflight secara otomatis, tapi header di actual response tetap dikontrol Lambda.

Verifikasi konfigurasi CORS di HTTP API:

aws apigatewayv2 get-api \
  --api-id YOUR_HTTP_API_ID \
  --region us-east-1 \
  --query 'CorsConfiguration'

Langkah 4: Diagnosis — Membedakan Masalah Preflight vs Actual Request

Mayoritas engineer langsung menyalahkan konfigurasi API Gateway, padahal masalahnya ada di Lambda response. Cara membedakannya:

graph TD A[CORS Error di Browser] --> B{Cek DevTools:
OPTIONS request status?} B -->|OPTIONS gagal / tidak ada| C[Masalah di API Gateway] B -->|OPTIONS berhasil 200| D{Cek actual request
response headers} D -->|Tidak ada Access-Control-Allow-Origin| E[Masalah di Lambda Response] D -->|Header ada tapi error lain| F[Cek Lambda logs
di CloudWatch] C --> G[Aktifkan CORS di console
dan deploy ulang] E --> H[Tambahkan CORS headers
di Lambda response] F --> I[Debug runtime error
di Lambda]
  1. Cek browser DevTools — Buka tab Network, filter OPTIONS. Jika OPTIONS request gagal (status 4xx atau tidak ada response header CORS), masalah ada di konfigurasi API Gateway.
  2. Jika OPTIONS berhasil tapi actual request masih error — Masalah ada di Lambda response. Header CORS tidak dikembalikan oleh Lambda.
  3. Cek actual response headers — Klik request yang gagal di DevTools, lihat tab Response Headers. Jika Access-Control-Allow-Origin tidak ada, Lambda tidak mengembalikannya.

Test preflight secara manual dengan curl untuk isolasi masalah:

curl -v -X OPTIONS \
  'https://YOUR_API_ID.execute-api.us-east-1.amazonaws.com/prod/users' \
  -H 'Origin: https://app.example.com' \
  -H 'Access-Control-Request-Method: POST' \
  -H 'Access-Control-Request-Headers: Content-Type,Authorization'

Response yang benar harus menyertakan Access-Control-Allow-Origin, Access-Control-Allow-Methods, dan Access-Control-Allow-Headers di header. Jika tidak ada, konfigurasi CORS di API Gateway belum benar atau belum di-deploy.

Test actual request:

curl -v -X POST \
  'https://YOUR_API_ID.execute-api.us-east-1.amazonaws.com/prod/users' \
  -H 'Origin: https://app.example.com' \
  -H 'Content-Type: application/json' \
  -d '{"name": "test"}'

Periksa response headers — Access-Control-Allow-Origin harus ada. Jika tidak, Lambda belum mengembalikan header CORS.

Pengalaman Nyata: Salah Diagnosis yang Membuang 2 Jam

Ini pola yang sering terjadi di production: developer mengaktifkan CORS di console, deploy, tapi error CORS masih muncul. Asumsi pertama selalu 'konfigurasi API Gateway-nya salah' — lalu menghabiskan waktu mengulang langkah yang sama berkali-kali.

Ternyata setelah dicek di DevTools, OPTIONS request berhasil dengan status 200 dan header CORS yang benar. Masalahnya ada di actual POST request — Lambda mengembalikan status 500 karena ada bug di kode, dan error response itu tidak menyertakan header CORS. Browser membaca 'tidak ada header CORS' dan melempar CORS error, padahal masalah sebenarnya adalah runtime error di Lambda.

Pelajarannya: error handler di Lambda juga harus mengembalikan header CORS. Jika Lambda melempar exception dan kamu tidak menanganinya dengan benar, response error tidak akan punya header CORS — dan browser akan melaporkan CORS error, bukan error yang sebenarnya.

Pastikan error handler Lambda juga menyertakan header CORS:

exports.handler = async (event) => {
  const corsHeaders = {
    'Access-Control-Allow-Origin': 'https://app.example.com',
    'Access-Control-Allow-Headers': 'Content-Type,Authorization',
    'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE,OPTIONS'
  };

  try {
    // logika utama
    return {
      statusCode: 200,
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
      body: JSON.stringify({ message: 'Success' })
    };
  } catch (error) {
    // error response JUGA harus punya CORS headers
    return {
      statusCode: 500,
      headers: { ...corsHeaders, 'Content-Type': 'application/json' },
      body: JSON.stringify({ error: 'Internal server error' })
    };
  }
};

IAM Policy untuk Akses API Gateway dan Lambda

Jika kamu perlu mengelola konfigurasi CORS via CLI atau automation, berikut policy minimal yang diperlukan:

🔽 Klik untuk melihat IAM policy untuk manajemen CORS API Gateway
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "apigateway:GET",
        "apigateway:PUT",
        "apigateway:POST",
        "apigateway:PATCH",
        "apigateway:DELETE"
      ],
      "Resource": [
        "arn:aws:apigateway:us-east-1::/restapis/YOUR_API_ID/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "lambda:GetFunction",
        "lambda:UpdateFunctionCode",
        "lambda:UpdateFunctionConfiguration"
      ],
      "Resource": "arn:aws:lambda:us-east-1:123456789012:function:YOUR_FUNCTION_NAME"
    }
  ]
}

Checklist CORS API Gateway — Verifikasi Sebelum Deploy

  • ☑ OPTIONS method sudah ada di setiap resource yang dipanggil frontend
  • Access-Control-Allow-Origin di konfigurasi API Gateway sesuai dengan origin frontend
  • ☑ API sudah di-deploy ulang ke stage setelah perubahan CORS
  • ☑ Lambda mengembalikan header Access-Control-Allow-Origin di setiap response, termasuk error response
  • ☑ Jika menggunakan credentials/cookies, Access-Control-Allow-Origin tidak boleh *
  • ☑ Header yang dikirim frontend (misalnya Authorization) sudah terdaftar di Access-Control-Allow-Headers

Wrap-Up dan Langkah Selanjutnya untuk Mengatasi CORS Error API Gateway

CORS error di API Gateway hampir selalu disebabkan oleh salah satu dari dua hal: konfigurasi preflight yang belum benar di API Gateway, atau Lambda yang tidak mengembalikan header CORS di actual response. Keduanya harus diselesaikan — tidak bisa hanya salah satu.

Untuk eksplorasi lebih lanjut, lihat dokumentasi resmi AWS:

Jika kamu menggunakan AWS SAM atau CloudFormation, CORS bisa dikonfigurasi langsung di template — ini menghilangkan risiko lupa deploy ulang setelah perubahan manual di console.

Glosarium

Istilah Penjelasan
CORS (Cross-Origin Resource Sharing) Mekanisme browser yang mengontrol akses resource dari origin berbeda menggunakan HTTP header.
Preflight Request HTTP OPTIONS request yang dikirim browser sebelum actual request untuk memverifikasi izin CORS dari server.
Lambda Proxy Integration Mode integrasi di mana API Gateway meneruskan request dan response antara client dan Lambda tanpa transformasi.
Access-Control-Allow-Origin HTTP response header yang menentukan origin mana yang diizinkan mengakses resource. Wajib ada di setiap response untuk request cross-origin.
Stage Deployment Proses publikasi perubahan konfigurasi API Gateway ke environment tertentu (dev, staging, prod). Tanpa deployment, perubahan tidak aktif.

Komentar

Postingan populer dari blog ini

EC2 Tidak Bisa Akses Internet di Custom VPC: Cara Pasang Internet Gateway dan Update Route Table