def _decode_and_verify_token(token, jwt_issuer): options = { 'require': ["exp", "iat"], 'verify_exp': api_settings.JWT_VERIFY_EXPIRATION, 'verify_aud': settings.JWT_AUTH.get('JWT_VERIFY_AUDIENCE', True), 'verify_iss': False, # TODO (ARCH-204): manually verify until issuer is configured correctly. 'verify_signature': False, # Verified with JWS already } decoded_token = jwt.decode( token, jwt_issuer['SECRET_KEY'], options=options, leeway=api_settings.JWT_LEEWAY, audience=jwt_issuer['AUDIENCE'], issuer=jwt_issuer['ISSUER'], algorithms=[api_settings.JWT_ALGORITHM], ) # TODO (ARCH-204): verify issuer manually until it is properly configured. token_issuer = decoded_token.get('iss') issuer_matched = any(issuer['ISSUER'] == token_issuer for issuer in get_jwt_issuers()) if not issuer_matched: logger.info('Token decode failed due to mismatched issuer [%s]', token_issuer) raise jwt.InvalidTokenError('%s is not a valid issuer.' % token_issuer) return decoded_token
def _decode_and_verify_token(token, jwt_issuer): options = { 'require_exp': True, 'require_iat': True, 'verify_exp': api_settings.JWT_VERIFY_EXPIRATION, 'verify_aud': settings.JWT_AUTH.get('JWT_VERIFY_AUDIENCE', True), 'verify_iss': False, # TODO (ARCH-204): manually verify until issuer is configured correctly. 'verify_signature': False, # Verified with JWS already } decoded_token = jwt.decode( token, jwt_issuer['SECRET_KEY'], api_settings.JWT_VERIFY, options=options, leeway=api_settings.JWT_LEEWAY, audience=jwt_issuer['AUDIENCE'], issuer=jwt_issuer['ISSUER'], algorithms=[api_settings.JWT_ALGORITHM], ) # TODO (ARCH-204): verify issuer manually until it is properly configured. token_issuer = decoded_token.get('iss') issuer_matched = any(issuer['ISSUER'] == token_issuer for issuer in get_jwt_issuers()) if not issuer_matched: logger.info('Token decode failed due to mismatched issuer [%s]', token_issuer) raise jwt.InvalidTokenError('%s is not a valid issuer.', token_issuer) return decoded_token
def test_get_deprecated_jwt_issuers(self): """ Verify the get_jwt_issuers operation returns the deprecated issuer information when current issuers are not configured for the system. """ _deprecated = [{ 'ISSUER': settings.JWT_AUTH['JWT_ISSUER'], 'SECRET_KEY': settings.JWT_AUTH['JWT_SECRET_KEY'], 'AUDIENCE': settings.JWT_AUTH['JWT_AUDIENCE'], }] mock_call = 'edx_rest_framework_extensions.settings._get_current_jwt_issuers' with mock.patch(mock_call, mock.Mock(return_value=None)): with warnings.catch_warnings(record=True) as warning_list: self.assertEqual(get_jwt_issuers(), _deprecated) warnings.simplefilter("default") self.assertEqual(len(warning_list), 1) self.assertTrue( issubclass(warning_list[-1].category, DeprecationWarning)) msg = "'JWT_ISSUERS' list not defined, checking for deprecated settings." self.assertIn(msg, str(warning_list[-1].message))
def jwt_decode_handler(token): """ Decodes a JSON Web Token (JWT). Notes: * Requires "exp" and "iat" claims to be present in the token's payload. * Supports multiple issuer decoding via settings.JWT_AUTH['JWT_ISSUERS'] (see below) * Aids debugging by logging DecodeError and InvalidTokenError log entries when decoding fails. Examples: Use with `djangorestframework-jwt <https://getblimp.github.io/django-rest-framework-jwt/>`_, by changing your Django settings: .. code-block:: python JWT_AUTH = { 'JWT_DECODE_HANDLER': 'edx_rest_framework_extensions.utils.jwt_decode_handler', 'JWT_ISSUER': 'https://the.jwt.issuer', 'JWT_SECRET_KEY': 'the-jwt-secret-key', (defaults to settings.SECRET_KEY) 'JWT_AUDIENCE': 'the-jwt-audience', } Enable multi-issuer support by specifying a list of dictionaries as settings.JWT_AUTH['JWT_ISSUERS']: .. code-block:: python JWT_ISSUERS = [ { 'ISSUER': 'test-issuer-1', 'SECRET_KEY': 'test-secret-key-1', 'AUDIENCE': 'test-audience-1', }, { 'ISSUER': 'test-issuer-2', 'SECRET_KEY': 'test-secret-key-2', 'AUDIENCE': 'test-audience-2', } ] Args: token (str): JWT to be decoded. Returns: dict: Decoded JWT payload. Raises: MissingRequiredClaimError: Either the exp or iat claims is missing from the JWT payload. InvalidTokenError: Decoding fails. """ options = { 'verify_exp': api_settings.JWT_VERIFY_EXPIRATION, 'verify_aud': settings.JWT_AUTH.get('JWT_VERIFY_AUDIENCE', True), 'require_exp': True, 'require_iat': True, } for jwt_issuer in get_jwt_issuers(): try: decoded = jwt.decode(token, jwt_issuer['SECRET_KEY'], api_settings.JWT_VERIFY, options=options, leeway=api_settings.JWT_LEEWAY, audience=jwt_issuer['AUDIENCE'], issuer=jwt_issuer['ISSUER'], algorithms=[api_settings.JWT_ALGORITHM]) return decoded except jwt.InvalidTokenError: msg = "Token decode failed for issuer '{issuer}'".format( issuer=jwt_issuer['ISSUER']) logger.info(msg, exc_info=True) msg = 'All combinations of JWT issuers and secret keys failed to validate the token.' logger.error(msg) raise jwt.InvalidTokenError(msg)
def test_get_current_jwt_issuers(self): """ Verify the get_jwt_issuers operation returns the current issuer information when configured """ self.assertEqual(get_jwt_issuers(), settings.JWT_AUTH['JWT_ISSUERS'])