Skip to content

Commit 344e490

Browse files
authored
Merge commit from fork
feat: add validation for ID tokens used as access tokens and update t…
2 parents feb1fe7 + 5abef5f commit 344e490

File tree

5 files changed

+69
-4
lines changed

5 files changed

+69
-4
lines changed

src/Exception/InvalidTokenException.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ final class InvalidTokenException extends Exception implements Auth0Exception
3737
*/
3838
public const MSG_BAD_SIGNATURE_MISSING_KID = 'Cannot verify signature: JWKS did not contain the key specified by the token';
3939

40+
/**
41+
* @var string
42+
*/
43+
public const MSG_ID_TOKEN_USED_AS_ACCESS_TOKEN = 'ID token cannot be validated as an access token (detected nonce claim)';
44+
4045
/**
4146
* @var string
4247
*/
@@ -214,6 +219,12 @@ public static function badSignatureMissingKid(
214219
return new self(self::MSG_BAD_SIGNATURE_MISSING_KID, 0, $previous);
215220
}
216221

222+
public static function idTokenUsedAsAccessToken(
223+
?Throwable $previous = null,
224+
): self {
225+
return new self(self::MSG_ID_TOKEN_USED_AS_ACCESS_TOKEN, 0, $previous);
226+
}
227+
217228
public static function jsonError(
218229
string $message,
219230
?Throwable $previous = null,

src/Token.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,15 @@ public function validate(
260260
$tokenOrganization ??= $this->configuration->getOrganization() ?? null;
261261
$tokenMaxAge ??= $this->configuration->getTokenMaxAge() ?? null;
262262
$tokenLeeway ??= $this->configuration->getTokenLeeway() ?? 60;
263-
if ($this->type !== self::TYPE_ACCESS_TOKEN) {
263+
264+
if (self::TYPE_ACCESS_TOKEN === $this->type) {
265+
if (null !== $this->getParser()->getClaim('nonce')) {
266+
throw InvalidTokenException::idTokenUsedAsAccessToken();
267+
}
268+
if ([] === $tokenAudience) {
269+
$tokenAudience[] = (string) $this->configuration->getClientId();
270+
}
271+
} else {
264272
$tokenAudience[] = (string) $this->configuration->getClientId();
265273
}
266274
$tokenAudience = array_unique($tokenAudience);

tests/Unit/Auth0Test.php

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -567,7 +567,7 @@ public function defer(
567567
test('decode() can be used with access tokens', function (): void {
568568
$token = (new TokenGenerator())->withHs256([
569569
'iss' => 'https://' . $this->configuration['domain'] . '/'
570-
]);
570+
], '__test_client_secret__', [], TokenGenerator::TOKEN_ACCESS);
571571

572572
$auth0 = new Auth0($this->configuration + [
573573
'tokenAlgorithm' => 'HS256',
@@ -586,6 +586,52 @@ public function defer(
586586
expect($decoded->getAudience())->toContain('__test_client_id__');
587587
});
588588

589+
test('decode() rejects ID tokens when validating as access tokens', function (): void {
590+
$idToken = (new TokenGenerator())->withHs256([
591+
'iss' => 'https://' . $this->configuration['domain'] . '/',
592+
'nonce' => '__test_nonce__',
593+
], '__test_client_secret__', [], TokenGenerator::TOKEN_ID);
594+
595+
$auth0 = new Auth0($this->configuration + [
596+
'tokenAlgorithm' => 'HS256',
597+
]);
598+
599+
$auth0->decode($idToken,
600+
null,
601+
null,
602+
null,
603+
null,
604+
null,
605+
null,
606+
Token::TYPE_ACCESS_TOKEN,
607+
);
608+
})->throws(InvalidTokenException::class, InvalidTokenException::MSG_ID_TOKEN_USED_AS_ACCESS_TOKEN);
609+
610+
test('decode() respects explicit audience for access tokens', function (): void {
611+
$apiAudience = 'https://api.example.com';
612+
$token = (new TokenGenerator())->withHs256([
613+
'iss' => 'https://' . $this->configuration['domain'] . '/',
614+
'aud' => $apiAudience
615+
], '__test_client_secret__', [], TokenGenerator::TOKEN_ACCESS);
616+
617+
$auth0 = new Auth0($this->configuration + [
618+
'tokenAlgorithm' => 'HS256',
619+
'audience' => [$apiAudience],
620+
]);
621+
622+
$decoded = $auth0->decode($token,
623+
null,
624+
null,
625+
null,
626+
null,
627+
null,
628+
null,
629+
Token::TYPE_ACCESS_TOKEN,
630+
);
631+
632+
expect($decoded->getAudience())->toEqual([$apiAudience]);
633+
});
634+
589635
test('decode() can be used with logout tokens', function (): void {
590636
$mockLogoutToken = TokenGenerator::create(TokenGenerator::TOKEN_LOGOUT, TokenGenerator::ALG_HS256, [
591637
'iss' => 'https://' . $this->configuration['domain'] . '/'

tests/Unit/TokenTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ function(): SdkConfiguration {
198198
return $this->configuration;
199199
},
200200
fn() => TokenGenerator::create(TokenGenerator::TOKEN_ACCESS, TokenGenerator::ALG_HS256)
201-
]])->throws(InvalidTokenException::class, InvalidTokenException::MSG_LOGOUT_TOKEN_NONCE_PRESENT);
201+
]])->throws(InvalidTokenException::class, InvalidTokenException::MSG_MISSING_EVENTS_CLAIM);
202202

203203
it('fails validating a Logout Token without `sub` or `sid` claims', function(
204204
SdkConfiguration $configuration

tests/Utilities/TokenGenerator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ protected static function getIdTokenClaims(
2626
'sid' => '__test_sid__',
2727
'iss' => 'https://domain.test/',
2828
'aud' => '__test_client_id__',
29-
'nonce' => '__test_nonce__',
29+
// 'nonce' => '__test_nonce__', Only ID tokens should have nonce claims
3030
'auth_time' => time() - 100,
3131
'exp' => time() + 1000,
3232
'iat' => time() - 1000,

0 commit comments

Comments
 (0)