Search code examples
symfonyjwtswaggeropenapiapi-platform.com

Api Platform OpenApi doesn't use the JWT token


I use ApiPlatform (PHP 8 / Symfony 6) to create a simple API with a JWT authentification. Authentification work correctly, I can generate a token. When I use PostMan to test a authenticated operation, no problem, I add manually the Bearer header with my token.

Now I would like to use the documentation auto generated to do this. I have created a JwtDecorator to add my login route and a security schema. Now I can add my token with the "Authorize" green button. But after, when I execute a authenticated operation, the token is not add in the header of the cUrl query. I don't understand why.

security.yaml

security:
    enable_authenticator_manager: true
    password_hashers:
        App\Entity\User: 'auto'
    providers:
        app_user_provider:
            entity:
                class: App\Entity\User
                property: email
    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        login:
            pattern: ^/login
            stateless: true
            json_login:
                check_path: /login
                success_handler: lexik_jwt_authentication.handler.authentication_success
                failure_handler: lexik_jwt_authentication.handler.authentication_failure
        api:
            pattern: ^/
            stateless: true
            jwt: ~
    access_control:
        - { path: ^/$, roles: PUBLIC_ACCESS } # Allows accessing the Swagger UI
        - { path: ^/docs, roles: PUBLIC_ACCESS } # Allows accessing the Swagger UI docs
        - { path: ^/login, roles: PUBLIC_ACCESS }
        - { path: ^/, roles: PUBLIC_ACCESS }

JwtDecorator.php

<?php

namespace App\OpenApi;

use ApiPlatform\OpenApi\Factory\OpenApiFactoryInterface;
use ApiPlatform\OpenApi\OpenApi;
use ApiPlatform\OpenApi\Model;

final class JwtDecorator implements OpenApiFactoryInterface
{
    public function __construct(
        private OpenApiFactoryInterface $decorated
    ) {}

    public function __invoke(array $context = []): OpenApi
    {
        $openApi = ($this->decorated)($context);
        $schemas = $openApi->getComponents()->getSchemas();

        $schemas['Token'] = new \ArrayObject([
            'type' => 'object',
            'properties' => [
                'token' => [
                    'type' => 'string',
                    'readOnly' => true,
                ],
            ],
        ]);
        $schemas['Credentials'] = new \ArrayObject([
            'type' => 'object',
            'properties' => [
                'username' => [
                    'type' => 'string',
                    'example' => 'test@gmail.com',
                ],
                'password' => [
                    'type' => 'string',
                    'example' => '123456',
                ],
            ],
        ]);

        $schemas = $openApi->getComponents()->getSecuritySchemes() ?? [];
        $schemas['JWT'] = new \ArrayObject([
            'type' => 'http',
            'scheme' => 'bearer',
            'bearerFormat' => 'JWT',
        ]);

        $pathItem = new Model\PathItem(
            ref: 'JWT Token',
            post: new Model\Operation(
                operationId: 'postCredentialsItem',
                tags: ['Token'],
                responses: [
                    '200' => [
                        'description' => 'Get JWT token',
                        'content' => [
                            'application/json' => [
                                'schema' => [
                                    '$ref' => '#/components/schemas/Token',
                                ],
                            ],
                        ],
                    ],
                ],
                summary: 'Get JWT token to login.',
                requestBody: new Model\RequestBody(
                    description: 'Generate new JWT Token',
                    content: new \ArrayObject([
                        'application/json' => [
                            'schema' => [
                                '$ref' => '#/components/schemas/Credentials',
                            ],
                        ],
                    ]),
                ),
                security: [],
            ),
        );
        $openApi->getPaths()->addPath('/login', $pathItem);

        return $openApi;
    }
}

NotificationCategory.php

<?php

namespace App\Entity;

use ...

#[ORM\Entity(repositoryClass: NotificationCategoryRepository::class)]
#[ApiResource(
    openapiContext: ['security' => [['Bearer Authentication' => []]]],
    security: "is_granted('ROLE_USER')"
)]
class NotificationCategory
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\Column(length: 255)]
    #[Groups(['read:Notification:get'])]
    private ?string $name = null;

    #[ORM\OneToMany(mappedBy: 'category', targetEntity: Notification::class)]
    private Collection $notifications;

    ...
}

Capture


Solution

  • I finally found. I was not using the correct schema name in my entity's openapiContext. In JwtDecorator file, I name the schema JWT, and in NotificationCategory entity : Bearer Authentication.

    So I replaced :

    #[ApiResource(
        openapiContext: ['security' => [['Bearer Authentication' => []]]],
        security: "is_granted('ROLE_USER')"
    )]
    

    By :

    #[ApiResource(
        openapiContext: ['security' => [['JWT' => []]]],
        security: "is_granted('ROLE_USER')"
    )]
    

    It work.