Yoast\WP\SEO\MyYoast_Client\Infrastructure\OIDC

ID_Token_Validator::validatepublicYoast 1.0

Validates an ID token.

Метод класса: ID_Token_Validator{}

Хуков нет.

Возвращает

Массив<Строку,. string|int|array<string>> The validated ID token payload (claims).

Использование

$ID_Token_Validator = new ID_Token_Validator();
$ID_Token_Validator->validate( $id_token, $client_id, $expected_nonce ): array;
$id_token(строка) (обязательный)
The raw ID token JWT.
$client_id(строка) (обязательный)
The expected client_id (audience).
$expected_nonce(строка) (обязательный)
The nonce sent in the authorization request.

Код ID_Token_Validator::validate() Yoast 27.7

public function validate( string $id_token, string $client_id, string $expected_nonce ): array {
	// Parse the header to get the kid.
	$parts = \explode( '.', $id_token );
	if ( \count( $parts ) !== 3 ) {
		throw new ID_Token_Validation_Exception( 'Invalid ID token format.' );
	}

	$header = \json_decode( Base64url::decode( $parts[0] ), true );
	if ( ! \is_array( $header ) ) {
		throw new ID_Token_Validation_Exception( 'Invalid ID token header.' );
	}

	if ( ( $header['alg'] ?? '' ) !== self::EXPECTED_ALG ) {
		// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Internal exception message.
		throw new ID_Token_Validation_Exception( 'Unsupported ID token algorithm: ' . ( $header['alg'] ?? 'none' ) );
	}

	// Fetch the public key from JWKS.
	$public_key = $this->get_public_key( ( $header['kid'] ?? '' ) );
	if ( $public_key === null ) {
		// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Internal exception message.
		throw new ID_Token_Validation_Exception( 'No matching key found in JWKS for kid: ' . ( $header['kid'] ?? 'none' ) );
	}

	// Verify signature and time-based claims (exp, nbf, iat).
	try {
		$result = $this->jwt_signer->verify( $id_token, $public_key );
	} catch ( JWT_Signature_Exception $e ) {
		// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Internal exception message.
		throw new ID_Token_Validation_Exception( 'ID token signature verification failed: ' . $e->getMessage(), 0, $e );
	} catch ( JWT_Validation_Exception $e ) {
		// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Internal exception message.
		throw new ID_Token_Validation_Exception( 'ID token rejected: ' . $e->getMessage(), 0, $e );
	}

	$payload = $result['payload'];

	// OIDC Core 1.0 §2: exp, iat and sub are required claims.
	foreach ( [ 'exp', 'iat', 'sub' ] as $required_claim ) {
		if ( ! isset( $payload[ $required_claim ] ) ) {
			// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Internal exception message.
			throw new ID_Token_Validation_Exception( 'ID token is missing required claim: ' . $required_claim );
		}
	}

	// Validate issuer.
	$expected_issuer = $this->discovery_client->get_document()->get_issuer();
	if ( ! \hash_equals( $expected_issuer, ( $payload['iss'] ?? '' ) ) ) {
		throw new ID_Token_Validation_Exception( 'ID token issuer mismatch.' );
	}

	// Validate audience.
	$aud = ( $payload['aud'] ?? '' );
	if ( \is_array( $aud ) ) {
		if ( ! \in_array( $client_id, $aud, true ) ) {
			throw new ID_Token_Validation_Exception( 'ID token audience does not contain client_id.' );
		}
		// OIDC Core 1.0 §2: when aud has multiple values, azp MUST equal client_id.
		$azp = ( $payload['azp'] ?? '' );
		if ( $azp !== '' && $azp !== $client_id ) {
			throw new ID_Token_Validation_Exception( 'ID token azp claim does not match client_id.' );
		}
	}
	elseif ( $aud !== $client_id ) {
		throw new ID_Token_Validation_Exception( 'ID token audience mismatch.' );
	}

	// Validate nonce.
	if ( ! \hash_equals( $expected_nonce, ( $payload['nonce'] ?? '' ) ) ) {
		throw new ID_Token_Validation_Exception( 'ID token nonce mismatch.' );
	}

	return $payload;
}