Esempio n. 1
0
    def verify(self, token, nonce=None, max_age=None):
        # Verify token presence
        if not token or not isinstance(token, str):
            raise TokenValidationError("ID token is required but missing.")

        # Verify algorithm and signature
        payload = self._sv.verify_signature(token)

        # Verify claims
        self._verify_payload(payload, nonce, max_age)
Esempio n. 2
0
    def get_key(self, key_id):
        keys = self._fetch_jwks()

        if keys and key_id in keys:
            return keys[key_id]

        if not self._cache_is_fresh:
            keys = self._fetch_jwks(force=True)
            if keys and key_id in keys:
                return keys[key_id]
        raise TokenValidationError(
            'RSA Public Key with ID "{}" was not found.'.format(key_id))
Esempio n. 3
0
    def verify_signature(self, token):
        try:
            header = jwt.get_unverified_header(token)
        except jwt.exceptions.DecodeError:
            raise TokenValidationError("ID token could not be decoded.")

        alg = header.get('alg', None)
        if alg != self._algorithm:
            raise TokenValidationError(
                'Signature algorithm of "{}" is not supported. Expected the ID token '
                'to be signed with "{}"'.format(alg, self._algorithm))

        kid = header.get('kid', None)
        secret_or_certificate = self._fetch_key(key_id=kid)

        try:
            decoded = jwt.decode(jwt=token,
                                 key=secret_or_certificate,
                                 algorithms=[self._algorithm],
                                 options=self.DISABLE_JWT_CHECKS)
        except jwt.exceptions.InvalidSignatureError:
            raise TokenValidationError("Invalid token signature.")
        return decoded
Esempio n. 4
0
    def _verify_payload(self, payload, nonce=None, max_age=None):
        try:
            # on Python 2.7, 'str' keys as parsed as 'unicode'
            # But 'unicode' was removed on Python 3.7
            # noinspection PyUnresolvedReferences
            ustr = unicode
        except NameError:
            ustr = str

        # Issuer
        if 'iss' not in payload or not isinstance(payload['iss'], (str, ustr)):
            raise TokenValidationError(
                'Issuer (iss) claim must be a string present in the ID token')
        if payload['iss'] != self.iss:
            raise TokenValidationError(
                'Issuer (iss) claim mismatch in the ID token; expected "{}", '
                'found "{}"'.format(self.iss, payload['iss']))

        # Subject
        if 'sub' not in payload or not isinstance(payload['sub'], (str, ustr)):
            raise TokenValidationError(
                'Subject (sub) claim must be a string present in the ID token')

        # Audience
        if 'aud' not in payload or not (isinstance(payload['aud'], (str, ustr))
                                        or isinstance(payload['aud'], list)):
            raise TokenValidationError(
                'Audience (aud) claim must be a string or array of strings present in the ID token'
            )

        if isinstance(payload['aud'], list) and not self.aud in payload['aud']:
            payload_audiences = ", ".join(payload['aud'])
            raise TokenValidationError(
                'Audience (aud) claim mismatch in the ID token; expected "{}" but was '
                'not one of "{}"'.format(self.aud, payload_audiences))
        elif isinstance(payload['aud'],
                        (str, ustr)) and payload['aud'] != self.aud:
            raise TokenValidationError(
                'Audience (aud) claim mismatch in the ID token; expected "{}" '
                'but found "{}"'.format(self.aud, payload['aud']))

        # --Time validation (epoch)--
        now = self._clock or time.time()
        leeway = self.leeway

        # Expires at
        if 'exp' not in payload or not isinstance(payload['exp'], int):
            raise TokenValidationError(
                'Expiration Time (exp) claim must be a number present in the ID token'
            )

        exp_time = payload['exp'] + leeway
        if now > exp_time:
            raise TokenValidationError(
                'Expiration Time (exp) claim error in the ID token; current time ({}) is '
                'after expiration time ({})'.format(now, exp_time))

        # Issued at
        if 'iat' not in payload or not isinstance(payload['iat'], int):
            raise TokenValidationError(
                'Issued At (iat) claim must be a number present in the ID token'
            )

        # Nonce
        if nonce:
            if 'nonce' not in payload or not isinstance(
                    payload['nonce'], (str, ustr)):
                raise TokenValidationError(
                    'Nonce (nonce) claim must be a string present in the ID token'
                )
            if payload['nonce'] != nonce:
                raise TokenValidationError(
                    'Nonce (nonce) claim mismatch in the ID token; expected "{}", '
                    'found "{}"'.format(nonce, payload['nonce']))

        # Authorized party
        if isinstance(payload['aud'], list) and len(payload['aud']) > 1:
            if 'azp' not in payload or not isinstance(payload['azp'],
                                                      (str, ustr)):
                raise TokenValidationError(
                    'Authorized Party (azp) claim must be a string present in the ID token when '
                    'Audience (aud) claim has multiple values')
            if payload['azp'] != self.aud:
                raise TokenValidationError(
                    'Authorized Party (azp) claim mismatch in the ID token; expected "{}", '
                    'found "{}"'.format(self.aud, payload['azp']))

        # Authentication time
        if max_age:
            if 'auth_time' not in payload or not isinstance(
                    payload['auth_time'], int):
                raise TokenValidationError(
                    'Authentication Time (auth_time) claim must be a number present in the ID token '
                    'when Max Age (max_age) is specified')

            auth_valid_until = payload['auth_time'] + max_age + leeway
            if now > auth_valid_until:
                raise TokenValidationError(
                    'Authentication Time (auth_time) claim in the ID token indicates that too much '
                    'time has passed since the last end-user authentication. Current time ({}) '
                    'is after last auth at ({})'.format(now, auth_valid_until))
Esempio n. 5
0
        user_id, name, mocker, authenticator: Auth0Authenticator):
    mocker.patch("auth0.v3.authentication.token_verifier.TokenVerifier.verify")
    token = jwt.encode({
        "sub": user_id,
        "name": name
    },
                       "secret",
                       algorithm="HS256").decode("utf8")

    result = (await authenticator.get_verified_user(token)).collapse()
    assert isinstance(result, User)
    assert result.id == str(user_id)
    assert result.name == name


@pytest.mark.parametrize("error", [TokenValidationError(), Exception()])
@pytest.mark.parametrize("user_id", [123456, "foo-id-283"])
@pytest.mark.parametrize("name", ["Foo User"])
@pytest.mark.asyncio
async def test_get_verified_user_returns_auth_error_on_invalid_token(
        user_id, name, error, mocker, authenticator: Auth0Authenticator):
    """
    Check that get_verified_user() returns an auth error with INVALID_TOKEN
    reason whenever token validation fails.
    """
    mocker.patch("auth0.v3.authentication.token_verifier.TokenVerifier.verify"
                 ).side_effect = error
    token = jwt.encode({
        "sub": user_id,
        "name": name
    },