---
title: "Add JWT Authentication to Your Symfony API"
description: "Secure your Symfony API using JWT authentication with JWKS."
canonicalUrl: "https://zuplo.com/use-cases/api-key-auth/php/symfony/jwt-backend"
framework: "Symfony"
language: "PHP"
authStrategy: "JWT with JWKS"
pageType: use-case
---

# Add JWT Authentication to Your Symfony API

Secure your Symfony API using JWT authentication with JWKS.

## How Zuplo Handles It

Let Zuplo issue short-lived JWTs signed with a JWKS your Symfony backend can verify — no long-lived API keys touch your origin.

## Symfony Backend Code

```php
<?php

// src/Security/JwtAuthenticator.php
namespace App\Security;

use Firebase\JWT\JWT;
use Firebase\JWT\JWK;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Cache\ItemInterface;

class JwtAuthenticator extends AbstractAuthenticator
{
    private const ISSUER = 'https://my-api-a32f34.zuplo.api/__zuplo/issuer';
    private const JWKS_URI = self::ISSUER . '/.well-known/jwks.json';

    public function __construct(private CacheInterface $cache) {}

    public function supports(Request $request): ?bool
    {
        return $request->headers->has('Authorization');
    }

    public function authenticate(Request $request): Passport
    {
        $authHeader = $request->headers->get('Authorization');

        if (!$authHeader || !str_starts_with($authHeader, 'Bearer ')) {
            throw new AuthenticationException('No token provided');
        }

        $token = substr($authHeader, 7);

        try {
            $jwks = $this->fetchJwks();
            $decoded = JWT::decode($token, JWK::parseKeySet($jwks), ['RS256']);

            if ($decoded->iss !== self::ISSUER) {
                throw new AuthenticationException('Invalid issuer');
            }

            return new SelfValidatingPassport(
                new UserBadge($decoded->sub, fn() => new JwtUser($decoded))
            );
        } catch (\Exception $e) {
            throw new AuthenticationException('Invalid token: ' . $e->getMessage());
        }
    }

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
    {
        return null; // Continue to controller
    }

    public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
    {
        return new JsonResponse([
            'error' => $exception->getMessage()
        ], Response::HTTP_UNAUTHORIZED);
    }

    private function fetchJwks(): array
    {
        return $this->cache->get('jwks', function (ItemInterface $item) {
            $item->expiresAfter(600);
            $response = file_get_contents(self::JWKS_URI);
            return json_decode($response, true);
        });
    }
}

// src/Controller/ProtectedController.php
namespace App\Controller;

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

class ProtectedController extends AbstractController
{
    #[Route('/protected', methods: ['GET'])]
    #[IsGranted('IS_AUTHENTICATED_FULLY')]
    public function index(): JsonResponse
    {
        return $this->json([
            'message' => 'Access granted',
            'user' => $this->getUser()
        ]);
    }
}
```

## Example Request

```bash
curl -X GET \
  'https://your-api.zuplo.dev/your-route' \
  -H 'Authorization: Bearer YOUR_API_KEY'
```

## Learn More

- [API Key Authentication on Zuplo](https://zuplo.com/docs/policies/api-key-auth-inbound)
- [JWT Authentication on Zuplo](https://zuplo.com/docs/policies/open-id-jwt-auth-inbound)
- [All use cases](https://zuplo.com/use-cases)
