Skip to content

Update JWT Documentation with the OpenApi way #1240

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 11, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 53 additions & 64 deletions core/jwt.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ If your API uses a [path prefix](https://symfony.com/doc/current/routing/externa
security:
encoders:
App\Entity\User:
algorithm: argon2i
algorithm: auto

# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
providers:
Expand Down Expand Up @@ -150,7 +150,7 @@ security:
failure_handler: lexik_jwt_authentication.handler.authentication_failure

access_control:
- { path: ^/api/docs, roles: IS_AUTHENTICATED_ANONYMOUSLY } # Allows accessing the Swagger UI
- { path: ^/docs, roles: IS_AUTHENTICATED_ANONYMOUSLY } # Allows accessing API documentations and Swagger UI
- { path: ^/authentication_token, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, roles: IS_AUTHENTICATED_FULLY }
```
Expand Down Expand Up @@ -181,9 +181,7 @@ All you have to do is configure the API key in the `value` field.
By default, [only the authorization header mode is enabled](https://github.com/lexik/LexikJWTAuthenticationBundle/blob/master/Resources/doc/index.md#2-use-the-token) in LexikJWTAuthenticationBundle.
You must set the [JWT token](https://github.com/lexik/LexikJWTAuthenticationBundle/blob/master/Resources/doc/index.md#1-obtain-the-token) as below and click on the "Authorize" button.

```
Bearer MY_NEW_TOKEN
```
Bearer MY_NEW_TOKEN

![Screenshot of API Platform with the configuration API Key](images/JWTConfigureApiKey.png)

Expand All @@ -193,108 +191,99 @@ We can add a `POST /authentication_token` endpoint to SwaggerUI to conveniently

![API Endpoint to retrieve JWT Token from SwaggerUI](images/jwt-token-swagger-ui.png)

To do it, we need to create a `SwaggerDecorator`:
To do it, we need to create a decorator:

```php
<?php
// api/src/OpenApi/JwtDecorator.php

declare(strict_types=1);

namespace App\Swagger;
namespace App\OpenApi;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use ApiPlatform\Core\OpenApi\Factory\OpenApiFactoryInterface;
use ApiPlatform\Core\OpenApi\OpenApi;
use ApiPlatform\Core\OpenApi\Model;

final class SwaggerDecorator implements NormalizerInterface
final class JwtDecorator implements OpenApiFactoryInterface
{
private NormalizerInterface $decorated;

public function __construct(NormalizerInterface $decorated)
{
$this->decorated = $decorated;
}

public function supportsNormalization($data, string $format = null): bool
{
return $this->decorated->supportsNormalization($data, $format);
}
public function __construct(
private OpenApiFactoryInterface $decorated
) {}

public function normalize($object, string $format = null, array $context = [])
public function __invoke(array $context = []): OpenApi
{
$docs = $this->decorated->normalize($object, $format, $context);
$openApi = ($this->decorated)($context);
$schemas = $openApi->getComponents()->getSchemas();

$docs['components']['schemas']['Token'] = [
$schemas['Token'] = new ArrayObject([
'type' => 'object',
'properties' => [
'token' => [
'type' => 'string',
'readOnly' => true,
],
],
];

$docs['components']['schemas']['Credentials'] = [
]);
$schemas['Credentials'] = new ArrayObject([
'type' => 'object',
'properties' => [
'username' => [
'email' => [
'type' => 'string',
'example' => 'api',
'example' => '[email protected]',
],
'password' => [
'type' => 'string',
'example' => 'api',
'example' => 'apassword',
],
],
];

$tokenDocumentation = [
'paths' => [
'/authentication_token' => [
'post' => [
'tags' => ['Token'],
'operationId' => 'postCredentialsItem',
'summary' => 'Get JWT token to login.',
'requestBody' => [
'description' => 'Create new JWT Token',
'content' => [
'application/json' => [
'schema' => [
'$ref' => '#/components/schemas/Credentials',
],
],
],
],
'responses' => [
Response::HTTP_OK => [
'description' => 'Get JWT token',
'content' => [
'application/json' => [
'schema' => [
'$ref' => '#/components/schemas/Token',
],
],
]);

$pathItem = new Model\PathItem(
ref: 'JWT Token',
post: new Model\Operation(
operationId: 'postCredentialsItem',
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',
],
],
]),
),
),
);
$openApi->getPaths()->addPath('/authentication_token', $pathItem);

return array_merge_recursive($docs, $tokenDocumentation);
return $openApi;
}
}
```

And register this service in `config/services.yaml`:

```yaml
# api/config/services.yaml
services:
# ...

App\Swagger\SwaggerDecorator:
decorates: 'api_platform.swagger.normalizer.documentation'
arguments: ['@App\Swagger\SwaggerDecorator.inner']
App\OpenApi\JwtDecorator:
decorates: 'api_platform.openapi.factory'
autoconfigure: false
```

Expand Down