# Symfony REST API Security in 2026: OAuth2, Rate Limiting and Interview Questions > Secure Symfony REST APIs with OAuth2 token introspection, rate limiting, and JWT validation. Covers Symfony 7.3 security features, common vulnerabilities, and technical interview questions. - Published: 2026-09-14 - Updated: 2026-09-14 - Author: Anthony Fillion-Maillet - Tags: symfony, security, oauth2, rest-api, rate-limiting, jwt - Reading time: 10 min --- Symfony REST API security requires a layered approach combining authentication, authorization, and traffic control. Symfony 7.3 introduced native OAuth2 token introspection support via RFC 7662, while the RateLimiter component provides built-in protection against abuse. > **Key Security Layers for Symfony APIs** > > A production-ready Symfony API needs three defenses: authentication (who is calling), authorization (what they can access), and rate limiting (how often). Missing any layer exposes the application to credential stuffing, data exfiltration, or denial of service. ## OAuth2 Token Introspection in Symfony 7.3 Symfony 7.3 adds built-in support for [OAuth2 Token Introspection (RFC 7662)](https://datatracker.ietf.org/doc/html/rfc7662). This feature validates access tokens by querying the authorization server directly, removing the need to decode tokens locally. This matters when the token format is opaque or controlled by a third-party identity provider. The `AccessTokenHandler` configuration accepts an introspection endpoint: ```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)%' ``` The introspection response contains `active`, `scope`, `client_id`, and optionally `username`. Symfony maps these to security attributes automatically. ```php // src/Controller/Api/ProfileController.php 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 already validated via introspection $user = $this->getUser(); return $this->json([ 'id' => $user->getId(), 'email' => $user->getEmail(), 'scopes' => $user->getRoles(), ]); } } ``` This removes the burden of JWT signature verification from the application. The authorization server handles key rotation, token revocation, and scope validation. ## Implementing Rate Limiting with the RateLimiter Component The [RateLimiter component](https://symfony.com/doc/current/rate_limiter.html) protects endpoints from abuse using token bucket or sliding window algorithms. Configuration happens in `framework.yaml`: ```yaml # config/packages/framework.yaml framework: rate_limiter: # General API rate limit: 100 requests per minute api_limiter: policy: sliding_window limit: 100 interval: '1 minute' # Stricter limit for authentication endpoints login_limiter: policy: token_bucket limit: 5 rate: { interval: '1 minute', amount: 5 } ``` Apply rate limiting to controllers using an event subscriber: ```php // src/EventSubscriber/RateLimitSubscriber.php 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(); // Only apply to API routes if (!str_starts_with($request->getPathInfo(), '/api')) { return; } // Use client IP or authenticated user as limiter key $key = $request->getClientIp(); $limiter = $this->apiLimiter->create($key); if (!$limiter->consume()->isAccepted()) { throw new TooManyRequestsHttpException(); } } } ``` For authenticated APIs, replace the IP-based key with the user identifier to prevent a single abusive user from affecting legitimate traffic from the same network. ## JWT Validation Without External Dependencies When the authorization server issues JWTs with a known public key, Symfony can validate tokens locally using the `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 ``` For self-issued JWTs without an OIDC provider, use `lexik/jwt-authentication-bundle`: ```php // src/Security/JwtTokenAuthenticator.php 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 { // Decodes and validates signature automatically $payload = $this->jwtManager->parse($accessToken); return new UserBadge($payload['sub']); } } ``` The JWT bundle handles RS256/ES256 signature verification, expiration checks, and issuer validation. Store the public key in `config/jwt/public.pem` and reference it in `lexik_jwt_authentication.yaml`. ## Securing API Endpoints with Voters Symfony Voters provide fine-grained authorization beyond simple role checks. A common pattern checks resource ownership: ```php // src/Security/Voter/ArticleVoter.php 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; // Admins can do anything if (in_array('ROLE_ADMIN', $user->getRoles(), true)) { return true; } // Authors can edit/delete their own articles return $article->getAuthor() === $user; } } ``` Use the voter in controllers: ```php #[Route('/api/articles/{id}', methods: ['PUT'])] public function update(Article $article, Request $request): JsonResponse { $this->denyAccessUnlessGranted(ArticleVoter::EDIT, $article); // Update logic here } ``` Voters centralize authorization logic and make it testable. They also appear frequently in [Symfony interview questions](/technologies/symfony/interview-questions/events-subscribers) about the security component. ## Common API Vulnerabilities and Mitigations The [OWASP API Security Top 10](https://owasp.org/API-Security/editions/2023/en/0x11-t10/) identifies recurring vulnerabilities in APIs. Symfony provides built-in protection for several: | Vulnerability | Symfony Mitigation | |---------------|--------------------| | Broken Object Level Authorization | Voters with ownership checks | | Broken Authentication | Login throttling, secure password hashing | | Excessive Data Exposure | DTO serialization groups | | Lack of Rate Limiting | RateLimiter component | | Mass Assignment | Form validation, DTO mapping | | Security Misconfiguration | Symfony security checker, env vars | For mass assignment protection, never hydrate entities directly from request data: ```php // src/Dto/CreateArticleDto.php 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, // Intentionally omit: author, createdAt, status // These are set by the application, not the client ) {} } ``` The DTO whitelist approach prevents clients from setting fields they should not control, such as `isAdmin` or `createdAt`. ## CORS Configuration for API Consumers Cross-Origin Resource Sharing headers control which domains can call the API from browsers. The `nelmio/cors-bundle` provides declarative configuration: ```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 ``` Avoid `allow_origin: ['*']` with `allow_credentials: true`. This combination exposes the API to credential theft via malicious sites. Use explicit domain whitelists or regex patterns. ## Technical Interview Questions on Symfony API Security These questions frequently appear in senior Symfony developer interviews. The answers reflect Symfony 7.3 capabilities. **Q: How does Symfony handle OAuth2 access token validation?** Symfony 7.3 supports three approaches: local JWT validation with signature verification, OIDC user info endpoint calls, and [RFC 7662 token introspection](https://symfony.com/blog/new-in-symfony-7-3-security-improvements). Introspection suits opaque tokens or scenarios where the app does not control the authorization server. The `AccessTokenHandler` abstracts all three behind a unified interface. **Q: What is the difference between firewalls and access control in Symfony?** Firewalls define authentication mechanisms per URL pattern. Access control rules define authorization requirements after authentication succeeds. A firewall might require a valid JWT, while access control requires `ROLE_ADMIN` for `/api/admin/*`. They operate in sequence: firewall authenticates, then access control authorizes. **Q: How would you prevent brute force attacks on a login endpoint?** Symfony's built-in login throttling limits failed attempts per username and IP. Configure `login_throttling` in the firewall: ```yaml security: firewalls: main: login_throttling: max_attempts: 5 interval: '15 minutes' ``` For API authentication, combine with the RateLimiter component on the token endpoint. Store attempt counts in Redis for multi-instance deployments. **Q: Explain Symfony Voters and when to use them over simple role checks.** Voters implement complex authorization logic that depends on the resource, not just the user role. Use voters when: the user must own the resource, the resource has a state machine (draft vs published), or authorization depends on business rules (subscription tier). Role checks suffice for static permissions like "only admins can access settings." For more Symfony security questions, see the [Symfony interview preparation guide](/blog/symfony/symfony-interview-questions). ## Building a Secure Symfony API: Key Takeaways - Configure OAuth2 token introspection for third-party identity providers where token format is opaque - Apply rate limiting at the infrastructure level (reverse proxy) and application level (RateLimiter component) for defense in depth - Use Voters for resource-level authorization, reserving role checks for static permissions - Map request data to DTOs with explicit properties to prevent mass assignment - Store secrets in environment variables, never in `config/*.yaml` files committed to version control - Test security rules with `WebTestCase` functional tests that verify both allowed and denied access - Audit dependencies with `symfony security:check` in CI pipelines --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/en/blog/symfony/symfony-rest-api-security-oauth2-rate-limiting-2026