Skip to content
Merged
Show file tree
Hide file tree
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
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Configuration (config/packages/api_platform_extras.yaml):
```yaml
api_platform_extras:
features:
# NOT IMPLEMENTED YET
http_cache:
enabled: false
schema_decoration:
Expand All @@ -13,12 +14,14 @@ api_platform_extras:
default_required_properties: false
#Add @id as an optional property to all POST, PUT and PATCH schemas.
jsonld_update_schema: false
# NOT IMPLEMENTED YET
simple_normalizer:
enabled: false
jwt_refresh:
enabled: false
auto_refresh_cookie: false
auto_refresh_header: false
user_aware: false
ignored_routes: []
ignored_paths: []
allowed_firewalls: []
Expand All @@ -41,6 +44,8 @@ Enable features by setting the corresponding flag to true.

If both auto-refresh flags are `false`, behavior is effectively the same as feature disabled.

`user_aware` defaults to `false`. When enabled, refresh token handling validates that the selected user provider supports the user class stored on the refresh token.

### Related bundle config

JWT/refresh token names and header prefix are taken from Lexik/Gesdinet config (with bundle defaults):
Expand All @@ -52,6 +57,29 @@ JWT/refresh token names and header prefix are taken from Lexik/Gesdinet config (

When Lexik extractor parameters are not exposed as container parameters, values are read from Lexik extractor service definition arguments.

### Refresh token entity

When using custom refresh token entities, extend the bundle entity:

```php
<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
#[ORM\Table(name: 'project_prefix_refresh_token')]
class RefreshToken extends \Netgen\ApiPlatformExtras\Entity\RefreshToken {}
```

And configure Gesdinet to use your entity:

```yaml
gesdinet_jwt_refresh_token:
refresh_token_class: App\Entity\RefreshToken
```

## Logout Configuration

Recommended config to invalidate both tokens and clear cookies with no custom app logic:
Expand Down
18 changes: 18 additions & 0 deletions config/doctrine/RefreshToken.orm.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>

<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">

<mapped-superclass name="Netgen\ApiPlatformExtras\Entity\RefreshToken" table="bhr_refresh_token" repository-class="Gesdinet\JWTRefreshTokenBundle\Entity\RefreshTokenRepository">
<id name="id" type="integer">
<generator strategy="AUTO"/>
</id>

<field name="refreshToken" type="string" column="refresh_token" length="128" unique="true"/>
<field name="username" type="string" length="255" column="username"/>
<field name="valid" type="datetime"/>
<field name="class" type="string" column="class" length="128"/>
</mapped-superclass>
</doctrine-mapping>
Original file line number Diff line number Diff line change
Expand Up @@ -138,12 +138,13 @@ public function process(ContainerBuilder $container): void
sprintf('lexik_jwt_authentication.cookie_provider.%s', $jwtCookieName),
ContainerInterface::NULL_ON_INVALID_REFERENCE,
),
$refreshTtl,
$refreshCookieConfig,
$jwtAuthorizationHeaderPrefix,
$refreshSingleUse,
$jwtAuthorizationHeaderName,
$jwtAuthorizationHeaderPrefix,
$jwtCookieName,
$refreshCookieConfig,
$refreshTtl,
$this->resolveBoolParameter($container, sprintf('%s.user_aware', self::BASE_FEATURE_PATH), false),
]);

$container->setDefinition(
Expand Down
4 changes: 4 additions & 0 deletions src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ public function getConfigTreeBuilder(): TreeBuilder
->defaultFalse()
->info('Will refresh jwt header during request cycle if valid refresh token present and enabled.')
->end()
->booleanNode('user_aware')
->defaultFalse()
->info('Will check if user provider supports class refresh token was created from.')
->end()
->arrayNode('ignored_routes')
->scalarPrototype()->end()
->defaultValue([])
Expand Down
9 changes: 9 additions & 0 deletions src/Entity/RefreshToken.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace Netgen\ApiPlatformExtras\Entity;

use Netgen\ApiPlatformExtras\Model\UserAwareRefreshToken;

class RefreshToken extends UserAwareRefreshToken {}
33 changes: 33 additions & 0 deletions src/Model/UserAwareRefreshToken.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace Netgen\ApiPlatformExtras\Model;

use Gesdinet\JWTRefreshTokenBundle\Model\AbstractRefreshToken;
use Symfony\Component\Security\Core\User\UserInterface;

abstract class UserAwareRefreshToken extends AbstractRefreshToken
{
protected string $class;

public static function createForUserWithTtl(string $refreshToken, UserInterface $user, int $ttl): static
{
/** @var static $token */
$token = parent::createForUserWithTtl($refreshToken, $user, $ttl);

return $token->setClass($user::class);
}

public function getClass(): string
{
return $this->class;
}

public function setClass(string $class): static
{
$this->class = $class;

return $this;
}
}
7 changes: 7 additions & 0 deletions src/NetgenApiPlatformExtrasBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,15 @@
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;

use function dirname;

final class NetgenApiPlatformExtrasBundle extends Bundle
{
public function getPath(): string
{
return dirname(__DIR__);
}

public function build(ContainerBuilder $container): void
{
$container
Expand Down
19 changes: 16 additions & 3 deletions src/Service/TokenRefreshService.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface;
use Netgen\ApiPlatformExtras\JwtRefresh\TokenSourceType;
use Netgen\ApiPlatformExtras\JwtRefresh\ValueObject\RefreshToken;
use Netgen\ApiPlatformExtras\Model\UserAwareRefreshToken;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\Request;
Expand All @@ -36,12 +37,13 @@ public function __construct(
private JWTTokenManagerInterface $jwtTokenManager,
private ServiceLocator $providerLocator,
private ?JWTCookieProvider $jwtCookieProvider,
private int $refreshTtl,
private array $refreshCookieSettings,
private string $jwtHeaderPrefix,
private bool $refreshSingleUse,
private string $jwtHeaderName,
private string $jwtHeaderPrefix,
private string $jwtCookieName,
private array $refreshCookieSettings,
private int $refreshTtl,
private bool $userAware,
) {
$this->refreshCookieSettings = array_merge([
'enabled' => false,
Expand Down Expand Up @@ -93,7 +95,18 @@ public function refresh(
return;
}

if ($this->userAware) {
if (!$refreshToken instanceof UserAwareRefreshToken) {
return;
}

if (!$provider->supportsClass($refreshToken->getClass())) {
return;
}
}

$username = $refreshToken->getUsername();

if (!is_string($username) || $username === '') {
return;
}
Expand Down