Skip to content

Commit c33509e

Browse files
authored
Merge pull request #36 from pdsinterop/fix/dpop
Change TokenGenerator::generateIdToken() to make DPOP optional when needed.
2 parents e5453e0 + 96de922 commit c33509e

File tree

2 files changed

+84
-33
lines changed

2 files changed

+84
-33
lines changed

src/TokenGenerator.php

Lines changed: 35 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -52,37 +52,45 @@ public function generateRegistrationAccessToken($clientId, $privateKey) {
5252
return $token->toString();
5353
}
5454

55-
public function generateIdToken($accessToken, $clientId, $subject, $nonce, $privateKey, $dpop, $now=null) {
55+
/**
56+
* Please note that the DPOP _is not_ required when requesting a token to
57+
* authorize a client but the DPOP _is_ required when requesting an access
58+
* token.
59+
*/
60+
public function generateIdToken($accessToken, $clientId, $subject, $nonce, $privateKey, $dpop=null, $now=null) {
5661
$issuer = $this->config->getServer()->get(OidcMeta::ISSUER);
5762

5863
$tokenHash = $this->generateTokenHash($accessToken);
5964

60-
// Create JWT
61-
$jwtConfig = Configuration::forSymmetricSigner(new Sha256(), InMemory::plainText($privateKey));
62-
$now = $now ?? new DateTimeImmutable();
63-
$useAfter = $now->sub(new \DateInterval('PT1S'));
64-
65-
$expire = $now->add($this->validFor);
66-
67-
$jkt = $this->makeJwkThumbprint($dpop);
68-
69-
$token = $jwtConfig->builder()
70-
->issuedBy($issuer)
71-
->permittedFor($clientId)
72-
->issuedAt($now)
73-
->canOnlyBeUsedAfter($useAfter)
74-
->expiresAt($expire)
75-
->withClaim("azp", $clientId)
76-
->relatedTo($subject)
77-
->identifiedBy($this->generateJti())
78-
->withClaim("nonce", $nonce)
79-
->withClaim("at_hash", $tokenHash) //FIXME: at_hash should only be added if the response_type is a token
80-
->withClaim("c_hash", $tokenHash) // FIXME: c_hash should only be added if the response_type is a code
81-
->withClaim("cnf", array(
82-
"jkt" => $jkt,
83-
))
84-
->getToken($jwtConfig->signer(), $jwtConfig->signingKey());
85-
return $token->toString();
65+
// Create JWT
66+
$jwtConfig = Configuration::forSymmetricSigner(new Sha256(), InMemory::plainText($privateKey));
67+
$now = $now ?? new DateTimeImmutable();
68+
$useAfter = $now->sub(new \DateInterval('PT1S'));
69+
70+
$expire = $now->add($this->validFor);
71+
72+
$token = $jwtConfig->builder()
73+
->issuedBy($issuer)
74+
->permittedFor($clientId)
75+
->issuedAt($now)
76+
->canOnlyBeUsedAfter($useAfter)
77+
->expiresAt($expire)
78+
->withClaim("azp", $clientId)
79+
->relatedTo($subject)
80+
->identifiedBy($this->generateJti())
81+
->withClaim("nonce", $nonce)
82+
->withClaim("at_hash", $tokenHash) //FIXME: at_hash should only be added if the response_type is a token
83+
->withClaim("c_hash", $tokenHash) // FIXME: c_hash should only be added if the response_type is a code
84+
;
85+
86+
if ($dpop !== null) {
87+
$jkt = $this->makeJwkThumbprint($dpop);
88+
$token = $token->withClaim("cnf", [
89+
"jkt" => $jkt,
90+
]);
91+
}
92+
93+
return $token->getToken($jwtConfig->signer(), $jwtConfig->signingKey())->toString();
8694
}
8795

8896
public function respondToRegistration($registration, $privateKey) {

tests/unit/TokenGeneratorTest.php

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -278,27 +278,70 @@ final public function testIdTokenGenerationWithoutPrivateKey(): void
278278
}
279279

280280
/**
281-
* @testdox Token Generator SHOULD complain WHEN asked to generate a IdToken without dpopKey
281+
* @testdox Token Generator SHOULD generate a token without Confirmation JWT Thumbprint (CNF JKT) WHEN asked to generate a IdToken without dpopKey
282282
*
283283
* @covers ::generateIdToken
284284
*/
285285
final public function testIdTokenGenerationWithoutDpopKey(): void
286286
{
287-
$tokenGenerator = $this->createTokenGenerator();
287+
$validFor = new \DateInterval('PT1S');
288288

289-
$this->expectArgumentCountError(6);
289+
$tokenGenerator = $this->createTokenGenerator($validFor);
290290

291-
$tokenGenerator->generateIdToken(
291+
292+
$mockServer = $this->getMockBuilder(ServerInterface::class)
293+
->disableOriginalConstructor()
294+
->getMock()
295+
;
296+
297+
$this->mockConfig->expects($this->once())
298+
->method('getServer')
299+
->willReturn($mockServer)
300+
;
301+
302+
$mockServer->expects($this->once())
303+
->method('get')
304+
->with(OidcMeta::ISSUER)
305+
->willReturn('mock issuer')
306+
;
307+
308+
$privateKey = file_get_contents(__DIR__.'/../fixtures/keys/private.key');
309+
310+
$now = new \DateTimeImmutable('1234-01-01 12:34:56.789');
311+
312+
$token = $tokenGenerator->generateIdToken(
292313
'mock access token',
293314
'mock clientId',
294315
'mock subject',
295316
'mock nonce',
296-
'mock private key'
317+
$privateKey,
318+
null,
319+
$now,
297320
);
321+
322+
$this->assertJwtEquals([
323+
[
324+
'typ' => 'JWT',
325+
'alg' => 'RS256',
326+
],
327+
[
328+
'at_hash' => '1EZBnvsFWlK8ESkgHQsrIQ',
329+
'aud' => 'mock clientId',
330+
'azp' => 'mock clientId',
331+
'c_hash' => '1EZBnvsFWlK8ESkgHQsrIQ',
332+
'exp' => -23225829903.789,
333+
'iat' => -23225829904.789,
334+
'iss' => 'mock issuer',
335+
'jti' => '4dc20036dbd8313ed055',
336+
'nbf' => -23225829905.789,
337+
'nonce' => 'mock nonce',
338+
'sub' => 'mock subject',
339+
],
340+
], $token);
298341
}
299342

300343
/**
301-
* @testdox Token Generator SHOULD return a IdToken WHEN asked to generate a IdToken with clientId and privateKey
344+
* @testdox Token Generator SHOULD return a IdToken with a Confirmation JWT Thumbprint (CNF JKT) WHEN asked to generate a IdToken with clientId and privateKey and DPOP
302345
*
303346
* @covers ::generateIdToken
304347
*

0 commit comments

Comments
 (0)