Skip to content

Commit 3fae64b

Browse files
authored
Merge pull request #6 from netgen/NGSTACK-1017-extending-refresh-token
Ngstack 1017 extending refresh token
2 parents 7c7b02b + 113fcbc commit 3fae64b

8 files changed

Lines changed: 119 additions & 6 deletions

File tree

README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Configuration (config/packages/api_platform_extras.yaml):
55
```yaml
66
api_platform_extras:
77
features:
8+
# NOT IMPLEMENTED YET
89
http_cache:
910
enabled: false
1011
schema_decoration:
@@ -13,12 +14,14 @@ api_platform_extras:
1314
default_required_properties: false
1415
#Add @id as an optional property to all POST, PUT and PATCH schemas.
1516
jsonld_update_schema: false
17+
# NOT IMPLEMENTED YET
1618
simple_normalizer:
1719
enabled: false
1820
jwt_refresh:
1921
enabled: false
2022
auto_refresh_cookie: false
2123
auto_refresh_header: false
24+
user_aware: false
2225
ignored_routes: []
2326
ignored_paths: []
2427
allowed_firewalls: []
@@ -41,6 +44,8 @@ Enable features by setting the corresponding flag to true.
4144

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

47+
`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.
48+
4449
### Related bundle config
4550

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

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

60+
### Refresh token entity
61+
62+
When using custom refresh token entities, extend the bundle entity:
63+
64+
```php
65+
<?php
66+
67+
namespace App\Entity;
68+
69+
use Doctrine\ORM\Mapping as ORM;
70+
71+
#[ORM\Entity]
72+
#[ORM\Table(name: 'project_prefix_refresh_token')]
73+
class RefreshToken extends \Netgen\ApiPlatformExtras\Entity\RefreshToken {}
74+
```
75+
76+
And configure Gesdinet to use your entity:
77+
78+
```yaml
79+
gesdinet_jwt_refresh_token:
80+
refresh_token_class: App\Entity\RefreshToken
81+
```
82+
5583
## Logout Configuration
5684

5785
Recommended config to invalidate both tokens and clear cookies with no custom app logic:
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
6+
https://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
7+
8+
<mapped-superclass name="Netgen\ApiPlatformExtras\Entity\RefreshToken" table="bhr_refresh_token" repository-class="Gesdinet\JWTRefreshTokenBundle\Entity\RefreshTokenRepository">
9+
<id name="id" type="integer">
10+
<generator strategy="AUTO"/>
11+
</id>
12+
13+
<field name="refreshToken" type="string" column="refresh_token" length="128" unique="true"/>
14+
<field name="username" type="string" length="255" column="username"/>
15+
<field name="valid" type="datetime"/>
16+
<field name="class" type="string" column="class" length="128"/>
17+
</mapped-superclass>
18+
</doctrine-mapping>

src/DependencyInjection/CompilerPass/JwtRefreshCompilerPass.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,12 +138,13 @@ public function process(ContainerBuilder $container): void
138138
sprintf('lexik_jwt_authentication.cookie_provider.%s', $jwtCookieName),
139139
ContainerInterface::NULL_ON_INVALID_REFERENCE,
140140
),
141-
$refreshTtl,
141+
$refreshCookieConfig,
142+
$jwtAuthorizationHeaderPrefix,
142143
$refreshSingleUse,
143144
$jwtAuthorizationHeaderName,
144-
$jwtAuthorizationHeaderPrefix,
145145
$jwtCookieName,
146-
$refreshCookieConfig,
146+
$refreshTtl,
147+
$this->resolveBoolParameter($container, sprintf('%s.user_aware', self::BASE_FEATURE_PATH), false),
147148
]);
148149

149150
$container->setDefinition(

src/DependencyInjection/Configuration.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ public function getConfigTreeBuilder(): TreeBuilder
5252
->defaultFalse()
5353
->info('Will refresh jwt header during request cycle if valid refresh token present and enabled.')
5454
->end()
55+
->booleanNode('user_aware')
56+
->defaultFalse()
57+
->info('Will check if user provider supports class refresh token was created from.')
58+
->end()
5559
->arrayNode('ignored_routes')
5660
->scalarPrototype()->end()
5761
->defaultValue([])

src/Entity/RefreshToken.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Netgen\ApiPlatformExtras\Entity;
6+
7+
use Netgen\ApiPlatformExtras\Model\UserAwareRefreshToken;
8+
9+
class RefreshToken extends UserAwareRefreshToken {}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Netgen\ApiPlatformExtras\Model;
6+
7+
use Gesdinet\JWTRefreshTokenBundle\Model\AbstractRefreshToken;
8+
use Symfony\Component\Security\Core\User\UserInterface;
9+
10+
abstract class UserAwareRefreshToken extends AbstractRefreshToken
11+
{
12+
protected string $class;
13+
14+
public static function createForUserWithTtl(string $refreshToken, UserInterface $user, int $ttl): static
15+
{
16+
/** @var static $token */
17+
$token = parent::createForUserWithTtl($refreshToken, $user, $ttl);
18+
19+
return $token->setClass($user::class);
20+
}
21+
22+
public function getClass(): string
23+
{
24+
return $this->class;
25+
}
26+
27+
public function setClass(string $class): static
28+
{
29+
$this->class = $class;
30+
31+
return $this;
32+
}
33+
}

src/NetgenApiPlatformExtrasBundle.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,15 @@
1212
use Symfony\Component\DependencyInjection\ContainerBuilder;
1313
use Symfony\Component\HttpKernel\Bundle\Bundle;
1414

15+
use function dirname;
16+
1517
final class NetgenApiPlatformExtrasBundle extends Bundle
1618
{
19+
public function getPath(): string
20+
{
21+
return dirname(__DIR__);
22+
}
23+
1724
public function build(ContainerBuilder $container): void
1825
{
1926
$container

src/Service/TokenRefreshService.php

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface;
1212
use Netgen\ApiPlatformExtras\JwtRefresh\TokenSourceType;
1313
use Netgen\ApiPlatformExtras\JwtRefresh\ValueObject\RefreshToken;
14+
use Netgen\ApiPlatformExtras\Model\UserAwareRefreshToken;
1415
use Symfony\Component\DependencyInjection\ServiceLocator;
1516
use Symfony\Component\HttpFoundation\Cookie;
1617
use Symfony\Component\HttpFoundation\Request;
@@ -36,12 +37,13 @@ public function __construct(
3637
private JWTTokenManagerInterface $jwtTokenManager,
3738
private ServiceLocator $providerLocator,
3839
private ?JWTCookieProvider $jwtCookieProvider,
39-
private int $refreshTtl,
40+
private array $refreshCookieSettings,
41+
private string $jwtHeaderPrefix,
4042
private bool $refreshSingleUse,
4143
private string $jwtHeaderName,
42-
private string $jwtHeaderPrefix,
4344
private string $jwtCookieName,
44-
private array $refreshCookieSettings,
45+
private int $refreshTtl,
46+
private bool $userAware,
4547
) {
4648
$this->refreshCookieSettings = array_merge([
4749
'enabled' => false,
@@ -93,7 +95,18 @@ public function refresh(
9395
return;
9496
}
9597

98+
if ($this->userAware) {
99+
if (!$refreshToken instanceof UserAwareRefreshToken) {
100+
return;
101+
}
102+
103+
if (!$provider->supportsClass($refreshToken->getClass())) {
104+
return;
105+
}
106+
}
107+
96108
$username = $refreshToken->getUsername();
109+
97110
if (!is_string($username) || $username === '') {
98111
return;
99112
}

0 commit comments

Comments
 (0)