Skip to content

Commit 6c22785

Browse files
authored
Refactor to JWT library (#441)
* Refactor to JWT library * harden tests, move errors to E_USER_WARNING for backward compatibility until 5.0 * test ttl and jti behaviour
1 parent 9ebd564 commit 6c22785

File tree

3 files changed

+124
-50
lines changed

3 files changed

+124
-50
lines changed

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
"psr/container": "^1.0 | ^2.0",
2020
"psr/http-client-implementation": "^1.0",
2121
"vonage/nexmo-bridge": "^0.1.0",
22-
"psr/log": "^1.1|^2.0|^3.0"
22+
"psr/log": "^1.1|^2.0|^3.0",
23+
"vonage/jwt": "^0.4.0"
2324
},
2425
"require-dev": {
2526
"guzzlehttp/guzzle": ">=6",

src/Client/Credentials/Keypair.php

Lines changed: 31 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,14 @@
1212
namespace Vonage\Client\Credentials;
1313

1414
use Lcobucci\JWT\Configuration;
15+
use Lcobucci\JWT\Encoding\JoseEncoder;
1516
use Lcobucci\JWT\Signer\Key;
1617
use Lcobucci\JWT\Signer\Key\InMemory;
1718
use Lcobucci\JWT\Signer\Rsa\Sha256;
1819
use Lcobucci\JWT\Token;
1920
use Vonage\Application\Application;
21+
use Vonage\Client\Exception\Validation;
22+
use Vonage\JWT\TokenGenerator;
2023

2124
use function base64_encode;
2225
use function mt_rand;
@@ -27,14 +30,9 @@
2730
*/
2831
class Keypair extends AbstractCredentials
2932
{
30-
/**
31-
* @var Key
32-
*/
33-
protected $key;
34-
35-
public function __construct($privateKey, $application = null)
33+
public function __construct(protected string $key, $application = null)
3634
{
37-
$this->credentials['key'] = $privateKey;
35+
$this->credentials['key'] = $key;
3836

3937
if ($application) {
4038
if ($application instanceof Application) {
@@ -43,71 +41,65 @@ public function __construct($privateKey, $application = null)
4341

4442
$this->credentials['application'] = $application;
4543
}
46-
47-
$this->key = InMemory::plainText($privateKey);
4844
}
4945

5046
/**
51-
* @return Key
47+
* @deprecated Old public signature using Lcobucci/Jwt directly
5248
*/
5349
public function getKey(): Key
50+
{
51+
return InMemory::plainText($this->key);
52+
}
53+
54+
public function getKeyRaw(): string
5455
{
5556
return $this->key;
5657
}
5758

5859
public function generateJwt(array $claims = []): Token
5960
{
60-
$config = Configuration::forSymmetricSigner(new Sha256(), $this->key);
61-
62-
$exp = time() + 60;
63-
$iat = time();
64-
$jti = base64_encode((string)mt_rand());
61+
$generator = new TokenGenerator($this->application, $this->getKeyRaw());
6562

6663
if (isset($claims['exp'])) {
67-
$exp = $claims['exp'];
68-
69-
unset($claims['exp']);
64+
// This will change to an Exception in 5.0
65+
trigger_error('Expiry date is automatically generated from now and TTL, so cannot be passed in
66+
as an argument in claims', E_USER_WARNING);
67+
unset($claims['nbf']);
7068
}
7169

72-
if (isset($claims['iat'])) {
73-
$iat = $claims['iat'];
74-
75-
unset($claims['iat']);
70+
if (isset($claims['ttl'])) {
71+
$generator->setTTL($claims['ttl']);
72+
unset($claims['ttl']);
7673
}
7774

7875
if (isset($claims['jti'])) {
79-
$jti = $claims['jti'];
80-
76+
$generator->setJTI($claims['jti']);
8177
unset($claims['jti']);
8278
}
8379

84-
$builder = $config->builder();
85-
$builder->issuedAt((new \DateTimeImmutable())->setTimestamp($iat))
86-
->expiresAt((new \DateTimeImmutable())->setTimestamp($exp))
87-
->identifiedBy($jti);
88-
8980
if (isset($claims['nbf'])) {
90-
$builder->canOnlyBeUsedAfter((new \DateTimeImmutable())->setTimestamp($claims['nbf']));
91-
81+
// Due to older versions of lcobucci/jwt, this claim has
82+
// historic fraction conversation issues. For now, nbf is not supported.
83+
// This will change to an Exception in 5.0
84+
trigger_error('NotBefore Claim is not supported in Vonage JWT', E_USER_WARNING);
9285
unset($claims['nbf']);
9386
}
9487

95-
if (isset($this->credentials['application'])) {
96-
$builder->withClaim('application_id', $this->credentials['application']);
97-
}
98-
9988
if (isset($claims['sub'])) {
100-
$builder->relatedTo($claims['sub']);
101-
89+
$generator->setSubject($claims['sub']);
10290
unset($claims['sub']);
10391
}
10492

10593
if (!empty($claims)) {
10694
foreach ($claims as $claim => $value) {
107-
$builder->withClaim($claim, $value);
95+
$generator->addClaim($claim, $value);
10896
}
10997
}
11098

111-
return $builder->getToken($config->signer(), $config->signingKey());
99+
$jwt = $generator->generate();
100+
$parser = new Token\Parser(new JoseEncoder());
101+
102+
// Backwards compatible for signature. In 5.0 this will return a string value
103+
return $parser->parse($jwt);
112104
}
113105
}

test/Client/Credentials/KeypairTest.php

Lines changed: 91 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
namespace VonageTest\Client\Credentials;
1111

12+
use Lcobucci\JWT\Signer\Key\InMemory;
13+
use Vonage\Client\Exception\Validation;
1214
use VonageTest\VonageTestCase;
1315
use Vonage\Client\Credentials\Keypair;
1416

@@ -19,8 +21,8 @@
1921

2022
class KeypairTest extends VonageTestCase
2123
{
22-
protected $key;
23-
protected $application = 'c90ddd99-9a5d-455f-8ade-dde4859e590e';
24+
protected string $key;
25+
protected string $application = 'c90ddd99-9a5d-455f-8ade-dde4859e590e';
2426

2527
public function setUp(): void
2628
{
@@ -36,6 +38,14 @@ public function testAsArray(): void
3638
$this->assertEquals($this->application, $array['application']);
3739
}
3840

41+
public function testGetKey(): void
42+
{
43+
$credentials = new Keypair($this->key, $this->application);
44+
45+
$key = $credentials->getKey();
46+
$this->assertInstanceOf(InMemory::class, $key);
47+
}
48+
3949
public function testProperties(): void
4050
{
4151
$credentials = new Keypair($this->key, $this->application);
@@ -48,7 +58,6 @@ public function testDefaultJWT(): void
4858
{
4959
$credentials = new Keypair($this->key, $this->application);
5060

51-
//could use the JWT object, but hope to remove as a dependency
5261
$jwt = (string)$credentials->generateJwt()->toString();
5362

5463
[$header, $payload] = $this->decodeJWT($jwt);
@@ -71,27 +80,100 @@ public function testAdditionalClaims(): void
7180
'nested' => [
7281
'data' => "something"
7382
]
74-
],
75-
'nbf' => 900
83+
]
84+
];
85+
86+
$jwt = $credentials->generateJwt($claims);
87+
[, $payload] = $this->decodeJWT($jwt->toString());
88+
89+
$this->assertArrayHasKey('arbitrary', $payload);
90+
$this->assertEquals($claims['arbitrary'], $payload['arbitrary']);
91+
}
92+
93+
public function testJtiClaim(): void
94+
{
95+
$credentials = new Keypair($this->key, $this->application);
96+
97+
$claims = [
98+
'jti' => '9a1b8ca6-4406-4530-9940-3cde9d41de3f'
99+
];
100+
101+
$jwt = $credentials->generateJwt($claims);
102+
[, $payload] = $this->decodeJWT($jwt->toString());
103+
104+
$this->assertArrayHasKey('jti', $payload);
105+
$this->assertEquals($claims['jti'], $payload['jti']);
106+
}
107+
108+
public function testTtlClaim(): void
109+
{
110+
$credentials = new Keypair($this->key, $this->application);
111+
112+
$claims = [
113+
'ttl' => 900
114+
];
115+
116+
$jwt = $credentials->generateJwt($claims);
117+
[, $payload] = $this->decodeJWT($jwt->toString());
118+
119+
$this->assertArrayHasKey('exp', $payload);
120+
$this->assertEquals(time() + 900, $payload['exp']);
121+
}
122+
123+
public function testNbfNotSupported(): void
124+
{
125+
set_error_handler(static function (int $errno, string $errstr) {
126+
throw new \Exception($errstr, $errno);
127+
}, E_USER_WARNING);
128+
129+
$this->expectExceptionMessage('NotBefore Claim is not supported in Vonage JWT');
130+
131+
$credentials = new Keypair($this->key, $this->application);
132+
133+
$claims = [
134+
'nbf' => time() + 900
76135
];
77136

78137
$jwt = $credentials->generateJwt($claims);
79138
[, $payload] = $this->decodeJWT($jwt->toString());
80139

81140
$this->assertArrayHasKey('arbitrary', $payload);
82141
$this->assertEquals($claims['arbitrary'], $payload['arbitrary']);
83-
$this->assertArrayHasKey('nbf', $payload);
84-
$this->assertEquals(900, $payload['nbf']);
142+
143+
restore_error_handler();
144+
}
145+
146+
public function testExpNotSupported(): void
147+
{
148+
set_error_handler(static function (int $errno, string $errstr) {
149+
throw new \Exception($errstr, $errno);
150+
}, E_USER_WARNING);
151+
152+
$this->expectExceptionMessage('Expiry date is automatically generated from now and TTL, so cannot be passed in
153+
as an argument in claims');
154+
155+
$credentials = new Keypair($this->key, $this->application);
156+
157+
$claims = [
158+
'exp' => time() + 900
159+
];
160+
161+
$jwt = $credentials->generateJwt($claims);
162+
[, $payload] = $this->decodeJWT($jwt->toString());
163+
164+
$this->assertArrayHasKey('arbitrary', $payload);
165+
$this->assertEquals($claims['arbitrary'], $payload['arbitrary']);
166+
167+
restore_error_handler();
85168
}
86169

87170
/**
88171
* @link https://github.com/Vonage/vonage-php-sdk-core/issues/276
89172
*/
90-
public function testExampleConversationJWTWorks()
173+
public function testExampleConversationJWTWorks(): void
91174
{
92175
$credentials = new Keypair($this->key, $this->application);
93176
$claims = [
94-
'exp' => strtotime(date('Y-m-d', strtotime('+24 Hours'))),
95177
'sub' => 'apg-cs',
96178
'acl' => [
97179
'paths' => [
@@ -113,7 +195,6 @@ public function testExampleConversationJWTWorks()
113195
[, $payload] = $this->decodeJWT($jwt->toString());
114196

115197
$this->assertArrayHasKey('exp', $payload);
116-
$this->assertEquals($claims['exp'], $payload['exp']);
117198
$this->assertEquals($claims['sub'], $payload['sub']);
118199
}
119200

0 commit comments

Comments
 (0)