public function __construct(array $options = [], $provider) { parent::__construct($options); if (!empty($options['id_token'])) { $this->idToken = $options['id_token']; $keys = $provider->getJwtVerificationKeys(); $idTokenClaims = null; try { $tks = explode('.', $this->idToken); // Check if the id_token contains signature if (count($tks) == 3 && !empty($tks[2])) { $idTokenClaims = (array) JWT::decode($this->idToken, $keys, ['RS256']); } else { // The id_token is unsigned (coming from v1.0 endpoint) - https://msdn.microsoft.com/en-us/library/azure/dn645542.aspx // Validate the access_token signature first by parsing it as JWT into claims $accessTokenClaims = (array) JWT::decode($options['access_token'], $keys, ['RS256']); // Then parse the idToken claims only without validating the signature $idTokenClaims = (array) JWT::jsonDecode(JWT::urlsafeB64Decode($tks[1])); } } catch (JWT_Exception $e) { throw new RuntimeException("Unable to parse the id_token!"); } if ($provider->getClientId() != $idTokenClaims['aud']) { throw new RuntimeException("The audience is invalid!"); } if ($idTokenClaims['nbf'] > time() || $idTokenClaims['exp'] < time()) { // Additional validation is being performed in firebase/JWT itself throw new RuntimeException("The id_token is invalid!"); } if ($provider->tenant == "common") { $provider->tenant = $idTokenClaims['tid']; $tenant = $provider->getTenantDetails($provider->tenant); if ($idTokenClaims['iss'] != $tenant['issuer']) { throw new RuntimeException("Invalid token issuer!"); } } else { $tenant = $provider->getTenantDetails($provider->tenant); if ($idTokenClaims['iss'] != $tenant['issuer']) { throw new RuntimeException("Invalid token issuer!"); } } $this->idTokenClaims = $idTokenClaims; } }
/** * Decodes a JWT string into a PHP object. * * @param string $jwt The JWT * @param string|array|null $key The key, or map of keys. * If the algorithm used is asymmetric, this is the public key * @param array $allowed_algs List of supported verification algorithms * Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256' * * @return object The JWT's payload as a PHP object * * @throws DomainException Algorithm was not provided * @throws UnexpectedValueException Provided JWT was invalid * @throws SignatureInvalidException Provided JWT was invalid because the signature verification failed * @throws BeforeValidException Provided JWT is trying to be used before it's eligible as defined by 'nbf' * @throws BeforeValidException Provided JWT is trying to be used before it's been created as defined by 'iat' * @throws ExpiredException Provided JWT has since expired, as defined by the 'exp' claim * * @uses jsonDecode * @uses urlsafeB64Decode */ public static function decode($jwt, $key, $allowed_algs = array()) { if (empty($key)) { throw new InvalidArgumentException('Key may not be empty'); } $tks = explode('.', $jwt); if (count($tks) != 3) { throw new UnexpectedValueException('Wrong number of segments'); } list($headb64, $bodyb64, $cryptob64) = $tks; if (null === ($header = JWT::jsonDecode(JWT::urlsafeB64Decode($headb64)))) { throw new UnexpectedValueException('Invalid header encoding'); } if (null === ($payload = JWT::jsonDecode(JWT::urlsafeB64Decode($bodyb64)))) { throw new UnexpectedValueException('Invalid claims encoding'); } $sig = JWT::urlsafeB64Decode($cryptob64); if (empty($header->alg)) { throw new DomainException('Empty algorithm'); } if (empty(self::$supported_algs[$header->alg])) { throw new DomainException('Algorithm not supported'); } if (!is_array($allowed_algs) || !in_array($header->alg, $allowed_algs)) { throw new DomainException('Algorithm not allowed'); } if (is_array($key) || $key instanceof \ArrayAccess) { if (isset($header->kid)) { $key = $key[$header->kid]; } else { throw new DomainException('"kid" empty, unable to lookup correct key'); } } // Check the signature if (!JWT::verify("{$headb64}.{$bodyb64}", $sig, $key, $header->alg)) { throw new SignatureInvalidException('Signature verification failed'); } // Check if the nbf if it is defined. This is the time that the // token can actually be used. If it's not yet that time, abort. if (isset($payload->nbf) && $payload->nbf > time() + self::$leeway) { throw new BeforeValidException('Cannot handle token prior to ' . date(DateTime::ISO8601, $payload->nbf)); } // Check that this token has been created before 'now'. This prevents // using tokens that have been created for later use (and haven't // correctly used the nbf claim). if (isset($payload->iat) && $payload->iat > time() + self::$leeway) { throw new BeforeValidException('Cannot handle token prior to ' . date(DateTime::ISO8601, $payload->iat)); } // Check if this token has expired. if (isset($payload->exp) && time() - self::$leeway >= $payload->exp) { throw new ExpiredException('Expired token', $payload); } return $payload; }
public static function decode($jwt, $valid_audiences, $client_secret, array $authorized_iss = []) { if (!is_array($valid_audiences)) { $valid_audiences = [$valid_audiences]; } $tks = explode('.', $jwt); if (count($tks) != 3) { throw new CoreException('Wrong number of segments'); } $headb64 = $tks[0]; $body64 = $tks[1]; $head = json_decode(JWT::urlsafeB64Decode($headb64)); if ($head->alg === 'RS256') { $body = json_decode(JWT::urlsafeB64Decode($body64)); if (!in_array($body->iss, $authorized_iss)) { throw new CoreException("We can't trust on a token issued by: `{$body->iss}`."); } $secret = self::fetch_public_key($body->iss); } elseif ($head->alg === 'HS256') { $secret = JWT::urlsafeB64Decode($client_secret); } else { throw new CoreException("Invalid signature algorithm"); } try { // Decode the user $decodedToken = JWT::decode($jwt, $secret, array('HS256', 'RS256')); // validate that this JWT was made for us $audience = $decodedToken->aud; if (!is_array($audience)) { $audience = [$audience]; } if (count(array_intersect($audience, $valid_audiences)) == 0) { throw new CoreException("This token is not intended for us."); } } catch (\Exception $e) { throw new CoreException($e->getMessage()); } return $decodedToken; }
public function verifyAndDecode($jwt) { $tks = explode('.', $jwt); if (count($tks) != 3) { throw new InvalidTokenException('Wrong number of segments'); } $headb64 = $tks[0]; $body64 = $tks[1]; $head = json_decode(JWT::urlsafeB64Decode($headb64)); if (!is_object($head) || !isset($head->alg)) { throw new InvalidTokenException("Invalid token"); } if (!in_array($head->alg, $this->suported_algs)) { throw new InvalidTokenException("Invalid signature algorithm"); } if ($head->alg === 'RS256') { $body = json_decode(JWT::urlsafeB64Decode($body64)); if (!in_array($body->iss, $this->authorized_iss)) { throw new CoreException("We can't trust on a token issued by: `{$body->iss}`."); } $secret = $this->JWKFetcher->fetchKeys($body->iss); } elseif ($head->alg === 'HS256') { if ($this->secret_base64_encoded) { $secret = JWT::urlsafeB64Decode($this->client_secret); } else { $secret = $this->client_secret; } } else { throw new InvalidTokenException("Invalid signature algorithm"); } try { // Decode the user $decodedToken = JWT::decode($jwt, $secret, array('HS256', 'RS256')); // validate that this JWT was made for us $audience = $decodedToken->aud; if (!is_array($audience)) { $audience = [$audience]; } if (count(array_intersect($audience, $this->valid_audiences)) == 0) { throw new InvalidTokenException("This token is not intended for us."); } } catch (\Exception $e) { throw new CoreException($e->getMessage()); } return $decodedToken; }
/** * * Create a public key represented in PEM format from RSA modulus and exponent information * * @param string $n the RSA modulus encoded in Base64 * @param string $e the RSA exponent encoded in Base64 * @return string the RSA public key represented in PEM format */ private static function createPemFromModulusAndExponent($n, $e) { $modulus = JWT::urlsafeB64Decode($n); $publicExponent = JWT::urlsafeB64Decode($e); $components = array('modulus' => pack('Ca*a*', 2, self::encodeLength(strlen($modulus)), $modulus), 'publicExponent' => pack('Ca*a*', 2, self::encodeLength(strlen($publicExponent)), $publicExponent)); $RSAPublicKey = pack('Ca*a*a*', 48, self::encodeLength(strlen($components['modulus']) + strlen($components['publicExponent'])), $components['modulus'], $components['publicExponent']); // sequence(oid(1.2.840.113549.1.1.1), null)) = rsaEncryption. $rsaOID = pack('H*', '300d06092a864886f70d0101010500'); // hex version of MA0GCSqGSIb3DQEBAQUA $RSAPublicKey = chr(0) . $RSAPublicKey; $RSAPublicKey = chr(3) . self::encodeLength(strlen($RSAPublicKey)) . $RSAPublicKey; $RSAPublicKey = pack('Ca*a*', 48, self::encodeLength(strlen($rsaOID . $RSAPublicKey)), $rsaOID . $RSAPublicKey); $RSAPublicKey = "-----BEGIN PUBLIC KEY-----\r\n" . chunk_split(base64_encode($RSAPublicKey), 64) . '-----END PUBLIC KEY-----'; return $RSAPublicKey; }