facile-it / php-openid-client

PHP OpenID Client
36 stars 7 forks source link

Bypassing the at_hash check for access token #38

Closed dsuciu119 closed 6 months ago

dsuciu119 commented 7 months ago

How could I bypass the at_hash check for the refresh token request, when the returned access token does not contain at_hash This can happen for example using Microsoft Azure, where at_hash is present only in the authorization code, not in the access token.

https://learn.microsoft.com/en-us/entra/identity-platform/id-token-claims-reference

As with the refresh token request, I noticed that after a new access token is obtained, at_hash is set as a mandatory claim and checked against it.

// my request
$tokenSet = $authorizationService->refresh(
    $client,
    $entity->getToken(),
    [
        'state' => $this->getState()
    ]
);

// vendor/facile-it/php-openid-client/src/Service/AuthorizationService.php
public function refresh(OpenIDClient $client, string $refreshToken, array $params = []): TokenSetInterface
{

    $tokenSet = $this->grant($client, array_merge($params, [
        'grant_type' => 'refresh_token',
        'refresh_token' => $refreshToken,
    ]));

    $idToken = $tokenSet->getIdToken();

    if (null === $idToken) {
        return $tokenSet;
    }

    $idToken = $tokenSet->getIdToken();

    if (null !== $idToken) {
        $claims = $this->idTokenVerifierBuilder->build($client)
            ->withAccessToken($tokenSet->getAccessToken())
            ->verify($idToken);
        $tokenSet = $tokenSet->withClaims($claims);
    }

    return $tokenSet;
}
// vendor/facile-it/php-jose-verifier/src/IdTokenVerifier.php
public function verify(string $jwt): array
{
    $jwt = $this->decrypt($jwt);

    try {
        $jws = (new CompactSerializer())->unserialize($jwt);
    } catch (InvalidArgumentException $e) {
        throw new InvalidTokenException('Invalid JWT provided', 0, $e);
    }

    $header = $jws->getSignature(0)->getProtectedHeader();

    $validator = $this->create($jwt);

    $requiredClaims = ['iss', 'sub', 'aud', 'exp', 'iat'];
    $alg = $header['alg'] ?? null;

    // at this step I have a new access token, but without at_hash in it
    if (null !== $this->accessToken) {
        $requiredClaims[] = 'at_hash';
        $validator = $validator->claim('at_hash', new AtHashChecker($this->accessToken, $alg ?: ''));
    }

    if (null !== $this->code) {
        $requiredClaims[] = 'c_hash';
        $validator = $validator->claim('c_hash', new CHashChecker($this->code, $alg ?: ''));
    }

    if (null !== $this->state) {
        $validator = $validator->claim('s_hash', new SHashChecker($this->state, $alg ?: ''));
    }

    /** @var Validate $validator */
    $validator = $validator->mandatory($requiredClaims);

    try {
        return $validator->run()->claims->all();
    } catch (Throwable $e) {
        throw $this->processException($e);
    }
}

In this case the error is the following:

#message: "The following claims are mandatory: at_hash."
#code: 0
#file: "/myapp/vendor/web-token/jwt-checker/ClaimCheckerManager.php"
#line: 94

Is there any way to get over this error? Thank you!

thomasvargiu commented 7 months ago

Hi @dsuciu119, I can confirm this is a bug. I will take a look on it on these days. Thank you!

thomasvargiu commented 6 months ago

Resolved: https://github.com/facile-it/php-jose-verifier/releases/tag/0.4.5