Skip to content

Commit

Permalink
Merge pull request #36 from pdsinterop/fix/dpop
Browse files Browse the repository at this point in the history
Change TokenGenerator::generateIdToken() to make DPOP optional when needed.
  • Loading branch information
ylebre authored Sep 30, 2022
2 parents e5453e0 + 96de922 commit c33509e
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 33 deletions.
62 changes: 35 additions & 27 deletions src/TokenGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,37 +52,45 @@ public function generateRegistrationAccessToken($clientId, $privateKey) {
return $token->toString();
}

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

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

// Create JWT
$jwtConfig = Configuration::forSymmetricSigner(new Sha256(), InMemory::plainText($privateKey));
$now = $now ?? new DateTimeImmutable();
$useAfter = $now->sub(new \DateInterval('PT1S'));

$expire = $now->add($this->validFor);

$jkt = $this->makeJwkThumbprint($dpop);

$token = $jwtConfig->builder()
->issuedBy($issuer)
->permittedFor($clientId)
->issuedAt($now)
->canOnlyBeUsedAfter($useAfter)
->expiresAt($expire)
->withClaim("azp", $clientId)
->relatedTo($subject)
->identifiedBy($this->generateJti())
->withClaim("nonce", $nonce)
->withClaim("at_hash", $tokenHash) //FIXME: at_hash should only be added if the response_type is a token
->withClaim("c_hash", $tokenHash) // FIXME: c_hash should only be added if the response_type is a code
->withClaim("cnf", array(
"jkt" => $jkt,
))
->getToken($jwtConfig->signer(), $jwtConfig->signingKey());
return $token->toString();
// Create JWT
$jwtConfig = Configuration::forSymmetricSigner(new Sha256(), InMemory::plainText($privateKey));
$now = $now ?? new DateTimeImmutable();
$useAfter = $now->sub(new \DateInterval('PT1S'));

$expire = $now->add($this->validFor);

$token = $jwtConfig->builder()
->issuedBy($issuer)
->permittedFor($clientId)
->issuedAt($now)
->canOnlyBeUsedAfter($useAfter)
->expiresAt($expire)
->withClaim("azp", $clientId)
->relatedTo($subject)
->identifiedBy($this->generateJti())
->withClaim("nonce", $nonce)
->withClaim("at_hash", $tokenHash) //FIXME: at_hash should only be added if the response_type is a token
->withClaim("c_hash", $tokenHash) // FIXME: c_hash should only be added if the response_type is a code
;

if ($dpop !== null) {
$jkt = $this->makeJwkThumbprint($dpop);
$token = $token->withClaim("cnf", [
"jkt" => $jkt,
]);
}

return $token->getToken($jwtConfig->signer(), $jwtConfig->signingKey())->toString();
}

public function respondToRegistration($registration, $privateKey) {
Expand Down
55 changes: 49 additions & 6 deletions tests/unit/TokenGeneratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -278,27 +278,70 @@ final public function testIdTokenGenerationWithoutPrivateKey(): void
}

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

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

$tokenGenerator->generateIdToken(

$mockServer = $this->getMockBuilder(ServerInterface::class)
->disableOriginalConstructor()
->getMock()
;

$this->mockConfig->expects($this->once())
->method('getServer')
->willReturn($mockServer)
;

$mockServer->expects($this->once())
->method('get')
->with(OidcMeta::ISSUER)
->willReturn('mock issuer')
;

$privateKey = file_get_contents(__DIR__.'/../fixtures/keys/private.key');

$now = new \DateTimeImmutable('1234-01-01 12:34:56.789');

$token = $tokenGenerator->generateIdToken(
'mock access token',
'mock clientId',
'mock subject',
'mock nonce',
'mock private key'
$privateKey,
null,
$now,
);

$this->assertJwtEquals([
[
'typ' => 'JWT',
'alg' => 'RS256',
],
[
'at_hash' => '1EZBnvsFWlK8ESkgHQsrIQ',
'aud' => 'mock clientId',
'azp' => 'mock clientId',
'c_hash' => '1EZBnvsFWlK8ESkgHQsrIQ',
'exp' => -23225829903.789,
'iat' => -23225829904.789,
'iss' => 'mock issuer',
'jti' => '4dc20036dbd8313ed055',
'nbf' => -23225829905.789,
'nonce' => 'mock nonce',
'sub' => 'mock subject',
],
], $token);
}

/**
* @testdox Token Generator SHOULD return a IdToken WHEN asked to generate a IdToken with clientId and privateKey
* @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
*
* @covers ::generateIdToken
*
Expand Down

0 comments on commit c33509e

Please sign in to comment.