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

# Add JWT Authentication to Your Laminas API

Secure your Laminas API using JWT authentication with JWKS.

## How Zuplo Handles It

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

## Laminas Backend Code

```php
<?php

use Firebase\JWT\JWT;
use Firebase\JWT\JWK;
use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Http\Message\ResponseInterface;

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

    private static ?array $jwksCache = null;
    private static ?int $jwksCacheTime = null;

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        $authHeader = $request->getHeaderLine('Authorization');

        if (empty($authHeader) || !str_starts_with($authHeader, 'Bearer ')) {
            return new JsonResponse(['error' => 'No token provided'], 401);
        }

        $token = substr($authHeader, 7);

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

            if ($decoded->iss !== self::ISSUER) {
                return new JsonResponse(['error' => 'Invalid issuer'], 401);
            }

            $request = $request->withAttribute('user', $decoded);
            return $handler->handle($request);
        } catch (\Exception $e) {
            return new JsonResponse([
                'error' => 'Invalid token',
                'details' => $e->getMessage()
            ], 401);
        }
    }

    private function fetchJwks(): array
    {
        if (self::$jwksCache === null || time() - self::$jwksCacheTime > 600) {
            $response = file_get_contents(self::JWKS_URI);
            self::$jwksCache = json_decode($response, true);
            self::$jwksCacheTime = time();
        }
        return self::$jwksCache;
    }
}

// In your route configuration
// $app->pipe(new JwtAuthMiddleware());

// Handler example
class ProtectedHandler implements RequestHandlerInterface
{
    public function handle(ServerRequestInterface $request): ResponseInterface
    {
        $user = $request->getAttribute('user');
        return new JsonResponse([
            'message' => 'Access granted',
            'user' => $user
        ]);
    }
}
```

## 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)
