Keamanan REST API Symfony di 2026: OAuth2, Rate Limiting, dan Pertanyaan Interview

Pelajari cara mengamankan REST API Symfony dengan OAuth2 token introspection, rate limiting, dan validasi JWT. Membahas fitur keamanan Symfony 7.3, kerentanan umum, dan pertanyaan interview teknis.

Arsitektur keamanan REST API Symfony dengan OAuth2 dan rate limiting

Keamanan REST API Symfony membutuhkan pendekatan berlapis yang menggabungkan autentikasi, otorisasi, dan kontrol lalu lintas. Symfony 7.3 memperkenalkan dukungan native OAuth2 token introspection melalui RFC 7662, sementara komponen RateLimiter menyediakan perlindungan bawaan terhadap penyalahgunaan.

Lapisan Keamanan Utama untuk API Symfony

API Symfony yang siap produksi membutuhkan tiga pertahanan: autentikasi (siapa yang memanggil), otorisasi (apa yang dapat diakses), dan rate limiting (seberapa sering). Mengabaikan salah satu lapisan akan mengekspos aplikasi terhadap credential stuffing, eksfiltrasi data, atau denial of service.

OAuth2 Token Introspection di Symfony 7.3

Symfony 7.3 menambahkan dukungan bawaan untuk OAuth2 Token Introspection (RFC 7662). Fitur ini memvalidasi access token dengan melakukan query langsung ke authorization server, menghilangkan kebutuhan untuk mendekode token secara lokal. Ini penting ketika format token bersifat opaque atau dikontrol oleh identity provider pihak ketiga.

Konfigurasi AccessTokenHandler menerima endpoint introspection:

yaml
# config/packages/security.yaml
security:
    firewalls:
        api:
            pattern: ^/api
            stateless: true
            access_token:
                token_handler:
                    introspection:
                        client_id: '%env(OAUTH_CLIENT_ID)%'
                        client_secret: '%env(OAUTH_CLIENT_SECRET)%'
                        introspection_url: '%env(OAUTH_INTROSPECTION_URL)%'

Response introspection berisi active, scope, client_id, dan opsional username. Symfony memetakan ini ke atribut keamanan secara otomatis.

src/Controller/Api/ProfileController.phpphp
namespace App\Controller\Api;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;

#[Route('/api/profile', methods: ['GET'])]
#[IsGranted('ROLE_USER')]
class ProfileController extends AbstractController
{
    public function __invoke(): JsonResponse
    {
        // Token sudah divalidasi melalui introspection
        $user = $this->getUser();
        
        return $this->json([
            'id' => $user->getId(),
            'email' => $user->getEmail(),
            'scopes' => $user->getRoles(),
        ]);
    }
}

Pendekatan ini menghilangkan beban verifikasi signature JWT dari aplikasi. Authorization server menangani rotasi key, pencabutan token, dan validasi scope.

Implementasi Rate Limiting dengan Komponen RateLimiter

Komponen RateLimiter melindungi endpoint dari penyalahgunaan menggunakan algoritma token bucket atau sliding window. Konfigurasi dilakukan di framework.yaml:

yaml
# config/packages/framework.yaml
framework:
    rate_limiter:
        # Rate limit API umum: 100 request per menit
        api_limiter:
            policy: sliding_window
            limit: 100
            interval: '1 minute'
            
        # Limit lebih ketat untuk endpoint autentikasi
        login_limiter:
            policy: token_bucket
            limit: 5
            rate: { interval: '1 minute', amount: 5 }

Terapkan rate limiting ke controller menggunakan event subscriber:

src/EventSubscriber/RateLimitSubscriber.phpphp
namespace App\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\RateLimiter\RateLimiterFactory;

final class RateLimitSubscriber implements EventSubscriberInterface
{
    public function __construct(
        private readonly RateLimiterFactory $apiLimiter,
    ) {}

    public static function getSubscribedEvents(): array
    {
        return [KernelEvents::REQUEST => ['onRequest', 10]];
    }

    public function onRequest(RequestEvent $event): void
    {
        if (!$event->isMainRequest()) {
            return;
        }
        
        $request = $event->getRequest();
        
        // Hanya terapkan ke route API
        if (!str_starts_with($request->getPathInfo(), '/api')) {
            return;
        }
        
        // Gunakan IP client atau user terautentikasi sebagai limiter key
        $key = $request->getClientIp();
        $limiter = $this->apiLimiter->create($key);
        
        if (!$limiter->consume()->isAccepted()) {
            throw new TooManyRequestsHttpException();
        }
    }
}

Untuk API terautentikasi, ganti key berbasis IP dengan identifier user untuk mencegah satu user yang menyalahgunakan mempengaruhi traffic legitimate dari jaringan yang sama.

Validasi JWT Tanpa Dependensi Eksternal

Ketika authorization server menerbitkan JWT dengan public key yang diketahui, Symfony dapat memvalidasi token secara lokal menggunakan OidcUserInfoTokenHandler:

yaml
# config/packages/security.yaml
security:
    firewalls:
        api:
            pattern: ^/api
            stateless: true
            access_token:
                token_handler:
                    oidc_user_info:
                        base_uri: '%env(OIDC_ISSUER)%'
                        claim: email

Untuk JWT yang diterbitkan sendiri tanpa provider OIDC, gunakan lexik/jwt-authentication-bundle:

src/Security/JwtTokenAuthenticator.phpphp
namespace App\Security;

use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface;
use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;

final class JwtTokenAuthenticator implements AccessTokenHandlerInterface
{
    public function __construct(
        private readonly JWTTokenManagerInterface $jwtManager,
    ) {}

    public function getUserBadgeFrom(string $accessToken): UserBadge
    {
        // Decode dan validasi signature secara otomatis
        $payload = $this->jwtManager->parse($accessToken);
        
        return new UserBadge($payload['sub']);
    }
}

Bundle JWT menangani verifikasi signature RS256/ES256, pengecekan expiration, dan validasi issuer. Simpan public key di config/jwt/public.pem dan referensikan di lexik_jwt_authentication.yaml.

Siap menguasai wawancara Symfony Anda?

Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.

Mengamankan Endpoint API dengan Voters

Symfony Voters menyediakan otorisasi fine-grained di luar pengecekan role sederhana. Pola umum memeriksa kepemilikan resource:

src/Security/Voter/ArticleVoter.phpphp
namespace App\Security\Voter;

use App\Entity\Article;
use App\Entity\User;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;

final class ArticleVoter extends Voter
{
    public const EDIT = 'ARTICLE_EDIT';
    public const DELETE = 'ARTICLE_DELETE';

    protected function supports(string $attribute, mixed $subject): bool
    {
        return in_array($attribute, [self::EDIT, self::DELETE], true)
            && $subject instanceof Article;
    }

    protected function voteOnAttribute(
        string $attribute,
        mixed $subject,
        TokenInterface $token
    ): bool {
        $user = $token->getUser();
        
        if (!$user instanceof User) {
            return false;
        }
        
        /** @var Article $article */
        $article = $subject;
        
        // Admin dapat melakukan apa saja
        if (in_array('ROLE_ADMIN', $user->getRoles(), true)) {
            return true;
        }
        
        // Penulis dapat mengedit/menghapus artikel mereka sendiri
        return $article->getAuthor() === $user;
    }
}

Gunakan voter di controller:

php
#[Route('/api/articles/{id}', methods: ['PUT'])]
public function update(Article $article, Request $request): JsonResponse
{
    $this->denyAccessUnlessGranted(ArticleVoter::EDIT, $article);
    
    // Logika update di sini
}

Voters memusatkan logika otorisasi dan membuatnya dapat diuji. Mereka juga sering muncul dalam pertanyaan interview Symfony tentang komponen keamanan.

Kerentanan API Umum dan Mitigasinya

OWASP API Security Top 10 mengidentifikasi kerentanan yang berulang dalam API. Symfony menyediakan perlindungan bawaan untuk beberapa di antaranya:

KerentananMitigasi Symfony
Broken Object Level AuthorizationVoters dengan pengecekan kepemilikan
Broken AuthenticationLogin throttling, secure password hashing
Excessive Data ExposureGrup serialisasi DTO
Lack of Rate LimitingKomponen RateLimiter
Mass AssignmentValidasi form, mapping DTO
Security MisconfigurationSecurity checker Symfony, env vars

Untuk perlindungan mass assignment, jangan pernah melakukan hydrate entity langsung dari data request:

src/Dto/CreateArticleDto.phpphp
namespace App\Dto;

use Symfony\Component\Validator\Constraints as Assert;

final class CreateArticleDto
{
    public function __construct(
        #[Assert\NotBlank]
        #[Assert\Length(max: 255)]
        public readonly string $title,
        
        #[Assert\NotBlank]
        public readonly string $content,
        
        // Sengaja dihilangkan: author, createdAt, status
        // Ini di-set oleh aplikasi, bukan client
    ) {}
}

Pendekatan whitelist DTO mencegah client mengatur field yang tidak seharusnya mereka kontrol, seperti isAdmin atau createdAt.

Konfigurasi CORS untuk Consumer API

Header Cross-Origin Resource Sharing mengontrol domain mana yang dapat memanggil API dari browser. Bundle nelmio/cors-bundle menyediakan konfigurasi deklaratif:

yaml
# config/packages/nelmio_cors.yaml
nelmio_cors:
    defaults:
        allow_origin: ['%env(CORS_ALLOW_ORIGIN)%']
        allow_methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS']
        allow_headers: ['Content-Type', 'Authorization']
        max_age: 3600
    paths:
        '^/api/':
            origin_regex: true
            allow_origin: ['^https://.*\.example\.com$']
            allow_credentials: true

Hindari allow_origin: ['*'] dengan allow_credentials: true. Kombinasi ini mengekspos API terhadap pencurian kredensial melalui situs berbahaya. Gunakan whitelist domain eksplisit atau pola regex.

Pertanyaan Interview Teknis tentang Keamanan API Symfony

Pertanyaan-pertanyaan ini sering muncul dalam interview developer Symfony senior. Jawaban mencerminkan kemampuan Symfony 7.3.

T: Bagaimana Symfony menangani validasi access token OAuth2?

Symfony 7.3 mendukung tiga pendekatan: validasi JWT lokal dengan verifikasi signature, panggilan endpoint user info OIDC, dan token introspection RFC 7662. Introspection cocok untuk token opaque atau skenario di mana aplikasi tidak mengontrol authorization server. AccessTokenHandler mengabstraksi ketiganya di balik interface yang seragam.

T: Apa perbedaan antara firewall dan access control di Symfony?

Firewall mendefinisikan mekanisme autentikasi per pola URL. Aturan access control mendefinisikan persyaratan otorisasi setelah autentikasi berhasil. Firewall mungkin membutuhkan JWT yang valid, sementara access control membutuhkan ROLE_ADMIN untuk /api/admin/*. Mereka beroperasi secara berurutan: firewall melakukan autentikasi, kemudian access control melakukan otorisasi.

T: Bagaimana cara mencegah serangan brute force pada endpoint login?

Login throttling bawaan Symfony membatasi percobaan gagal per username dan IP. Konfigurasikan login_throttling di firewall:

yaml
security:
    firewalls:
        main:
            login_throttling:
                max_attempts: 5
                interval: '15 minutes'

Untuk autentikasi API, kombinasikan dengan komponen RateLimiter pada endpoint token. Simpan hitungan percobaan di Redis untuk deployment multi-instance.

T: Jelaskan Symfony Voters dan kapan menggunakannya dibanding pengecekan role sederhana.

Voters mengimplementasikan logika otorisasi kompleks yang bergantung pada resource, bukan hanya role user. Gunakan voters ketika: user harus memiliki resource, resource memiliki state machine (draft vs published), atau otorisasi bergantung pada aturan bisnis (tier subscription). Pengecekan role cukup untuk permission statis seperti "hanya admin yang dapat mengakses settings."

Untuk pertanyaan keamanan Symfony lainnya, lihat panduan persiapan interview Symfony.

Mulai berlatih!

Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.

Membangun API Symfony yang Aman: Poin-Poin Penting

  • Konfigurasikan OAuth2 token introspection untuk identity provider pihak ketiga di mana format token bersifat opaque
  • Terapkan rate limiting di level infrastruktur (reverse proxy) dan level aplikasi (komponen RateLimiter) untuk defense in depth
  • Gunakan Voters untuk otorisasi level resource, cadangkan pengecekan role untuk permission statis
  • Petakan data request ke DTO dengan properti eksplisit untuk mencegah mass assignment
  • Simpan secret di environment variable, jangan pernah di file config/*.yaml yang di-commit ke version control
  • Uji aturan keamanan dengan functional test WebTestCase yang memverifikasi akses yang diizinkan dan ditolak
  • Audit dependency dengan symfony security:check di pipeline CI
Tantangan harian

Bisakah kamu menemukan bug di Symfony?

Satu potongan kode nyata, satu bug tersembunyi, satu percobaan per hari. Tanpa akun untuk mencoba.

Anthony Fillion-Maillet

Ditulis oleh

Anthony Fillion-Maillet

Pendiri SharpSkill

Developer fullstack selama lebih dari 10 tahun. Ia menjalankan SharpSkill dan bertanggung jawab atas semua yang diterbitkan di sini.

Diperbarui 14 September 2026

Tag

#symfony
#security
#oauth2
#rest-api
#rate-limiting
#jwt

Bagikan

Artikel terkait