docs: def verify_jwt(jwt,
                 pub_key=None,
                 allowed_algs=None,
                 iat_skew=timedelta(),
                 checks_optional=False,
                 ignore_not_implemented=False):
      """
      Verify a JSON Web Token.
  
      :param jwt: The JSON Web Token to verify.
      :type jwt: str or unicode
  
      :param pub_key: The public key to be used to verify the token. Note: if you pass ``None`` and **allowed_algs** contains ``none`` then the token's signature will not be verified.
      :type pub_key: `jwcrypto.jwk.JWK <https://jwcrypto.readthedocs.io/en/latest/jwk.html>`_
  
      :param allowed_algs: Algorithms expected to be used to sign the token. The ``in`` operator is used to test membership.
      :type allowed_algs: list or NoneType (meaning an empty list)
  
      :param iat_skew: The amount of leeway to allow between the issuer's clock and the verifier's clock when verifiying that the token was generated in the past. Defaults to no leeway.
      :type iat_skew: datetime.timedelta
  
      :param checks_optional: If ``False``, then the token must contain the **typ** header property and the **iat**, **nbf** and **exp** claim properties.
      :type checks_optional: bool
  
      :param ignore_not_implemented: If ``False``, then the token must *not* contain the **jku**, **jwk**, **x5u**, **x5c** or **x5t** header properties.
      :type ignore_not_implemented: bool
  
      :rtype: tuple
      :returns: ``(header, claims)`` if the token was verified successfully. The token must pass the following tests:
  
      - Its header must contain a property **alg** with a value in **allowed_algs**.
      - Its signature must verify using **pub_key** (unless its algorithm is ``none`` and ``none`` is in **allowed_algs**).
      - If the corresponding property is present or **checks_optional** is ``False``:
  
        - Its header must contain a property **typ** with the value ``JWT``.
        - Its claims must contain a property **iat** which represents a date in the past (taking into account :obj:`iat_skew`).
        - Its claims must contain a property **nbf** which represents a date in the past.
        - Its claims must contain a property **exp** which represents a date in the future.
  
      :raises: If the token failed to verify.
      """
      if allowed_algs is None:
          allowed_algs = []
  
      if not isinstance(allowed_algs, list):
          # jwcrypto only supports list of allowed algorithms
          raise _JWTError('allowed_algs must be a list')
  
      header, claims, _ = jwt.split('.')
  
      parsed_header = json_decode(base64url_decode(header))
  
      alg = parsed_header.get('alg')
      if alg is None:
          raise _JWTError('alg header not present')
      if alg not in allowed_algs:
          raise _JWTError('algorithm not allowed: ' + alg)
  
      if not ignore_not_implemented:
          for k in parsed_header:
              if k not in JWSHeaderRegistry:
                  raise _JWTError('unknown header: ' + k)
              if not JWSHeaderRegistry[k].supported:
                  raise _JWTError('header not implemented: ' + k)
  
      if pub_key:
          token = JWS()
          token.allowed_algs = allowed_algs
          token.deserialize(jwt, pub_key)
      elif 'none' not in allowed_algs:
          raise _JWTError('no key but none alg not allowed')
  
      parsed_claims = json_decode(base64url_decode(claims))
  
      utcnow = datetime.utcnow()
      now = timegm(utcnow.utctimetuple())
  
      typ = parsed_header.get('typ')
      if typ is None:
          if not checks_optional:
              raise _JWTError('typ header not present')
      elif typ != 'JWT':
          raise _JWTError('typ header is not JWT')
  
      iat = parsed_claims.get('iat')
      if iat is None:
          if not checks_optional:
              raise _JWTError('iat claim not present')
      elif iat > timegm((utcnow + iat_skew).utctimetuple()):
          raise _JWTError('issued in the future')
  
      nbf = parsed_claims.get('nbf')
      if nbf is None:
          if not checks_optional:
              raise _JWTError('nbf claim not present')
      elif nbf > now:
          raise _JWTError('not yet valid')
  
      exp = parsed_claims.get('exp')
      if exp is None:
          if not checks_optional:
              raise _JWTError('exp claim not present')
      elif exp <= now:
          raise _JWTError('expired')
  
      return parsed_header, parsed_claims