AizuDemy

Panduan Amankan API FastAPI dari OWASP Top 10 untuk Production

Panduan Amankan API FastAPI dari OWASP Top 10 untuk Production

Latar Belakang & Lanskap Ancaman OWASP API Top 10

FastAPI populer. Performa ASGI tinggi. Validasi data otomatis via Pydantic. Kecepatan tanpa isolasi keamanan: hancur. Penyerang eksploitasi endpoint mudah. Wajib pasang proteksi bertapis. OWASP API Security Top 10 acuan standar. Identifikasi celah fatal backend API modern:

  • API1:2023 Broken Object Level Authorization (BOLA): Endpoint terima ID objek tanpa cek kepemilikan resource. Pengguna A ubah ID di path URL (misal /api/v1/orders/99 ke /api/v1/orders/100). Data Pengguna B bocor. Query ORM lupa filter user_id.
  • API2:2023 Broken Authentication: Token JWT tidak ditandatangani benar. Algoritma none diizinkan. SECRET_KEY pendek, mudah dibobol brute-force. Expiry time absen.
  • API3:2023 Broken Object Property Level Authorization: Eksploitasi Mass Assignment & Excessive Data Exposure. Payload JSON terima field terlarang is_admin: true. Database update otomatis. Endpoint kembalikan seluruh kolom DB termasuk password_hash.
  • API4:2023 Unrestricted Resource Consumption: Endpoint tanpa limitasi request. Tanpa pagination limit. Penyerang minta 1.000.000 record sekaligus. CPU 100%, RAM habis. Server Denial of Service (DoS).
  • API5:2023 Broken Function Level Authorization (BFLA): Pentransferan hak akses fungsi gagal. User reguler panggil endpoint administratif DELETE /api/v1/users/5. Otorisasi role absen pada route handler.
  • API6:2023 Unrestricted Access to Sensitive Business Flows: Bot eksploitasi alur bisnis sensitif. Pembelian tiket, claim voucher, registrasi akun massal. Otomatisasi tanpa bot detection.
  • API7:2023 Server Side Request Forgery (SSRF): Endpoint terima URL dari input user. Server lakukan HTTP request ke URL tersebut tanpa filter. Penyerang paksa server akses metadata cloud internal (http://169.254.169.254/latest/meta-data/).
  • API8:2023 Security Misconfiguration: CORS dikonfigurasi *. Swagger UI aktif di production. Header keamanan HTTP absen. Log aplikasi bocorkan token JWT.
  • API9:2023 Improper Inventory Management: Endpoint versi lama (/api/v1/) tetap aktif tanpa patch. Lingkungan staging terakses publik tanpa otentikasi.
  • API10:2023 Unsafe Consumption of APIs: Aplikasi percaya penuh data dari API pihak ketiga. Tidak ada sanitasi output pihak ketiga sebelum masuk ke database internal.

Praktek Middleware: Strict CORS, Trusted Host, & Rate Limiting

Amankan layer terdepan FastAPI. Cegah request jahat sebelum masuk route handler.

1. Restriksi Strict CORS & Trusted Host

Wildcard origin (*) pada production: bahaya fatal. Izinkan domain terverifikasi saja. Tambah TrustedHostMiddleware cegah HTTP Host Header Attack.

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.trustedhost import TrustedHostMiddleware

app = FastAPI(docs_url=None, redoc_url=None)

# Restricted domain origin
ALLOWED_ORIGINS = [
    "https://app.domainanda.com",
    "https://admin.domainanda.com"
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=ALLOWED_ORIGINS,
    allow_credentials=True,
    allow_methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
    allow_headers=["Authorization", "Content-Type", "X-Request-ID"],
)

# Host header attack prevention
app.add_middleware(
    TrustedHostMiddleware,
    allowed_hosts=["api.domainanda.com", "*.domainanda.com"]
)

2. Rate Limiting Terdistribusi dengan Slowapi

Membatasi request per IP. Cegah brute-force login dan serangan DoS resource intensive. Integrasi slowapi dengan backend Redis.

from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded
from fastapi import Request, FastAPI

limiter = Limiter(key_func=get_remote_address, storage_uri="redis://localhost:6379/0")
app = FastAPI()
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)

@app.post("/api/v1/auth/login")
@limiter.limit("5/minute")
async def login(request: Request):
    return {"status": "proses_otentikasi"}

@app.get("/api/v1/reports/export")
@limiter.limit("2/hour")
async def export_report(request: Request):
    return {"status": "proses_export"}

Otentikasi Strict JWT & Proteksi BOLA/BFLA

Ekstraksi klaim JWT. Validasi signature. Terapkan verifikasi kepemilikan resource langsung di query layer (Pencegahan BOLA).

import jwt
from datetime import datetime, timezone
from typing import Annotated
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from pydantic import BaseModel

SECRET_KEY = "c8f3b2d1e4a5f67890123456789abcdef0123456789abcdef0123456789abcde" # Min 64 hex chars
ALGORITHM = "HS256"

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login")

class TokenPayload(BaseModel):
    sub: int
    role: str
    exp: int

async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]) -> TokenPayload:
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Kredensial otentikasi tidak valid",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM], options={"verify_exp": True})
        user_id: int = payload.get("sub")
        role: str = payload.get("role")
        exp: int = payload.get("exp")
        if user_id is None or role is None:
            raise credentials_exception
        return TokenPayload(sub=user_id, role=role, exp=exp)
    except jwt.PyJWTError:
        raise credentials_exception

def require_role(required_role: str):
    def role_checker(current_user: TokenPayload = Depends(get_current_user)):
        if current_user.role != required_role and current_user.role != "admin":
            raise HTTPException(status_code=403, detail="Akses ditolak: Hak akses tidak mencukupi")
        return current_user
    return role_checker

@app.get("/api/v1/orders/{order_id}")
async def get_order(
    order_id: int, 
    current_user: Annotated[TokenPayload, Depends(get_current_user)]
):
    # Query DB wajib memuat pengkondisian user_id (Mencegah BOLA)
    # SIMULASI: Fetch dari DB menggunakan parameter terisolasi
    order = await fetch_order_by_id_and_owner(order_id=order_id, owner_id=current_user.sub)
    if not order:
        # Kembalikan 404 bukan 403 agar tidak membocorkan keberadaan ID objek milik user lain
        raise HTTPException(status_code=404, detail="Order tidak ditemukan")
    return order

@app.delete("/api/v1/users/{target_user_id}", dependencies=[Depends(require_role("admin"))])
async def delete_user(target_user_id: int):
    # Mencegah BFLA dengan penegakan peran (Role-Based Access Control)
    return {"status": f"User {target_user_id} berhasil dihapus"}

Validasi Input Pydantic V2 & Prevent Mass Assignment

Sanitasi data masuk. Mencegah Injection Attack (SQLi, Command Injection, XSS) dan Mass Assignment (API3:2023). Gunakan Pydantic V2 `extra='forbid'`.

from pydantic import BaseModel, Field, EmailStr, ConfigDict, field_validator
import re

class RegisterInputSchema(BaseModel):
    # Tolak semua field yang tidak terdefinisi di skema (Anti Mass Assignment)
    model_config = ConfigDict(extra="forbid", str_strip_whitespace=True)

    username: str = Field(..., min_length=3, max_length=30, pattern="^[a-zA-Z0-9_]+$")
    email: EmailStr
    password: str = Field(..., min_length=12, max_length=128)

    @field_validator("password")
    @classmethod
    def validate_password_complexity(cls, v: str) -> str:
        if not re.search(r"[A-Z]", v):
            raise ValueError("Password wajib mengandung minimal 1 huruf besar")
        if not re.search(r"[a-z]", v):
            raise ValueError("Password wajib mengandung minimal 1 huruf kecil")
        if not re.search(r"[0-9]", v):
            raise ValueError("Password wajib mengandung minimal 1 angka")
        if not re.search(r"[^a-zA-Z0-9]", v):
            raise ValueError("Password wajib mengandung minimal 1 karakter spesial")
        return v

class UserResponseSchema(BaseModel):
    # Hanya field ini yang dipublikasikan ke HTTP response
    model_config = ConfigDict(from_attributes=True)
    
    id: int
    username: str
    email: EmailStr
    # password_hash, salt, is_admin TIDAK dimasukkan di sini

Pengerasan HTTP Response Security Headers

Injeksi HTTP response headers standar industri. Proteksi browser-side dari XSS, Clickjacking, MIME sniffing.

from starlette.middleware.base import BaseHTTPMiddleware
from fastapi import Request, Response

class SecurityHeadersMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next) -> Response:
        response: Response = await call_next(request)
        response.headers["X-Content-Type-Options"] = "nosniff"
        response.headers["X-Frame-Options"] = "DENY"
        response.headers["X-XSS-Protection"] = "1; mode=block"
        response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains; preload"
        response.headers["Content-Security-Policy"] = "default-src 'none'; frame-ancestors 'none'; sandbox"
        response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
        response.headers["Permissions-Policy"] = "geolocation=(), microphone=(), camera=()"
        # Hapus informasi server bawaan
        response.headers["Server"] = "Protected-API"
        return response

app.add_middleware(SecurityHeadersMiddleware)

Checklist Hardening Production & CI/CD Security Pipeline

  • Nonaktifkan Swagger UI & ReDoc: Pastikan dokumentasi interaktif nonaktif di lingkungan produksi. Set parameter docs_url=None, redoc_url=None, dan openapi_url=None pada penginstansiasi kelas FastAPI() jika lingkungan berjalan pada moda production.
  • Isolasi Rahasia via Variable Lingkungan: Jangan pernah menyimpan kredensial database atau SECRET_KEY secara hardcoded di codebase. Manfaatkan modul pydantic-settings untuk membaca nilai dari file .env terisolasi yang diinjeksikan oleh secret manager.
  • Jalankan Aplikasi dengan User Non-Root: Konfigurasi Dockerfile untuk membuat grup dan pengguna sistem khusus tanpa hak akses root. Eksekusi proses Uvicorn atau Gunicorn menggunakan identitas non-privileged tersebut guna membatasi dampak jika terjadi pemutusan container (container breakout).
  • Terapkan Reverse Proxy & TLS Termination: Tempatkan Nginx, Traefik, atau Cloudflare di depan server ASGI Uvicorn. Konfigurasikan TLS 1.3, SSL Certificate renewal otomatis, HTTP request size limiting, serta pembatasan koneksi di tingkat proxy.
  • Audit Vulnerability Dependensi Python: Integrasikan pengujian keamanan otomatis pada pipeline CI/CD menggunakan alat seperti pip-audit, safety, atau trivy untuk mendeteksi pustaka terinstal yang memiliki kerentanan CVE sebelum deployment dilakukan.
  • Sanitasi System Logging & Handling Data Sensitif: Terapkan kustomisasi formatter pada modul logging FastAPI. Pastikan parameter sensitif seperti token otentikasi Bearer, kata sandi, data nomor kartu kredit, dan PII (Personally Identifiable Information) tersanitasi atau di-masking sebelum ditulis ke berkas log server.

Kesimpulan

Pengamanan API FastAPI tingkat production butuh pendekatan Defense-in-Depth. Skema validasi Pydantic V2 ketat. Middleware CORS dan Rate Limiting terisolasi. Otorisasi data-layer pencegah BOLA. Terapkan seluruh lapisan keamanan. Sistem backend aman dari ancaman lanskap OWASP API Security Top 10.

📖 Artikel Terkait