def authenticate(self, request): """ Returns a two-tuple of `User` and token if a valid signature has been supplied using JWT-based authentication. Otherwise returns `None`. """ try: token = self.get_token_from_request(request) if token is None: return None except MissingToken: return None try: payload = self.jwt_decode_token(token) except ExpiredSignature: msg = _('Token has expired.') raise exceptions.AuthenticationFailed(msg) except jwt.DecodeError: msg = _('Error decoding token.') raise exceptions.AuthenticationFailed(msg) except jwt.InvalidTokenError: msg = _('Invalid token.') raise exceptions.AuthenticationFailed(msg) if apps.is_installed('rest_framework_jwt.blacklist'): from rest_framework_jwt.blacklist.models import BlacklistedToken if BlacklistedToken.is_blocked(token, payload): msg = _('Token is blacklisted.') raise exceptions.PermissionDenied(msg) user = self.authenticate_credentials(payload) return user, token
def validate(self, data): token = data['token'] payload = check_payload(token=token) user = check_user(payload=payload) # Get and check 'orig_iat' orig_iat = payload.get('orig_iat') if orig_iat is None: msg = _('orig_iat field not found in token.') raise serializers.ValidationError(msg) # Verify expiration refresh_limit = \ api_settings.JWT_REFRESH_EXPIRATION_DELTA.total_seconds() expiration_timestamp = orig_iat + refresh_limit now_timestamp = unix_epoch() if now_timestamp > expiration_timestamp: msg = _('Refresh has expired.') raise serializers.ValidationError(msg) new_payload = JSONWebTokenAuthentication.jwt_create_payload(user) new_payload['orig_iat'] = orig_iat return { 'token': JSONWebTokenAuthentication.jwt_encode_payload(new_payload), 'user': user, 'issued_at': new_payload.get('iat', unix_epoch()) }
def authenticate(self, request): """ Returns a two-tuple of `User` and token if a valid signature has been supplied using JWT-based authentication. Otherwise returns `None`. """ try: token = self.get_token_from_request(request) except MissingToken: return None if apps.is_installed('rest_framework_jwt.blacklist'): if BlacklistedToken.objects.filter( token=force_str(token)).exists(): msg = _('Token is blacklisted.') raise exceptions.PermissionDenied(msg) try: payload = self.jwt_decode_token(token) except jwt.ExpiredSignature: msg = _('Token has expired.') raise exceptions.AuthenticationFailed(msg) except jwt.DecodeError: msg = _('Error decoding token.') raise exceptions.AuthenticationFailed(msg) user = self.authenticate_credentials(payload) return user, token
def check_user(payload): from rest_framework_jwt.authentication import JSONWebTokenAuthentication username = JSONWebTokenAuthentication. \ jwt_get_username_from_payload(payload) if not username: msg = _('Invalid token.') raise serializers.ValidationError(msg) if api_settings.JWT_TOKEN_ID == 'require' and not payload.get('jti'): msg = _('Invalid token.') raise serializers.ValidationError(msg) # Make sure user exists try: User = get_user_model() user = User.objects.get_by_natural_key(username) except User.DoesNotExist: msg = _("User doesn't exist.") raise serializers.ValidationError(msg) if not user.is_active: msg = _('User account is disabled.') raise serializers.ValidationError(msg) return user
class IsNotBlacklisted(BasePermission): message = _('You have been blacklisted.') def has_permission(self, request, view): return not BlacklistedToken.objects.filter( token=JSONWebTokenAuthentication.get_token_from_request( request)).exists()
def authenticate_credentials(self, payload): """ Returns an active user that matches the payload's user id. """ username = self.jwt_get_username_from_payload(payload) if not username: msg = _('Invalid payload.') raise exceptions.AuthenticationFailed(msg) try: user = User.objects.get(user_id=username) except User.DoesNotExist: msg = _('Invalid token.') raise exceptions.AuthenticationFailed(msg) return user
def check_payload(token): from rest_framework_jwt.authentication import JSONWebTokenAuthentication if apps.is_installed('rest_framework_jwt.blacklist'): if BlacklistedToken.objects.filter(token=force_str(token)).exists(): msg = _('Token is blacklisted.') raise serializers.ValidationError(msg) try: payload = JSONWebTokenAuthentication.jwt_decode_token(token) except jwt.ExpiredSignature: msg = _('Token has expired.') raise serializers.ValidationError(msg) except jwt.DecodeError: msg = _('Error decoding token.') raise serializers.ValidationError(msg) return payload
def test_view_requires_authentication(api_client): url = reverse("test-view") response = api_client.get(url) expected_output = { "detail": _("Authentication credentials were not provided.") } assert response.status_code == status.HTTP_401_UNAUTHORIZED assert response.json() == expected_output
def test_refresh_limit_expired__returns_validation_error( call_auth_refresh_endpoint, user): payload = JSONWebTokenAuthentication.jwt_create_payload(user) payload["orig_iat"] = 0 # beginning of time auth_token = JSONWebTokenAuthentication.jwt_encode_payload(payload) expected_output = {"non_field_errors": [_("Refresh has expired.")]} response = call_auth_refresh_endpoint(auth_token) assert response.json() == expected_output
def test_token_with_invalid_username_returns_validation_error( user, call_auth_verify_endpoint ): payload = JSONWebTokenAuthentication.jwt_create_payload(user) payload["username"] = "******" auth_token = JSONWebTokenAuthentication.jwt_encode_payload(payload) expected_output = {"non_field_errors": [_("User doesn't exist.")]} verify_response = call_auth_verify_endpoint(auth_token) assert verify_response.json() == expected_output
def test_refresh_with_invalid_algorithm__returns_validation_error( call_auth_refresh_endpoint, user): header = '{"alg": "bad", "typ": "JWT"}' token_bytes = base64.b64encode( header.encode('ascii')) + "..".encode('ascii') token = token_bytes.decode() expected_output = {"non_field_errors": [_("Invalid token.")]} refresh_response = call_auth_refresh_endpoint(token) assert refresh_response.json() == expected_output
def test_expired_token__returns_validation_error(call_auth_refresh_endpoint, user): payload = JSONWebTokenAuthentication.jwt_create_payload(user) payload["iat"] = 0 payload["exp"] = 1 auth_token = JSONWebTokenAuthentication.jwt_encode_payload(payload) expected_output = {"non_field_errors": [_("Token has expired.")]} refresh_response = call_auth_refresh_endpoint(auth_token) assert refresh_response.json() == expected_output
def test_view_returns_401_for_invalid_token(api_client): token = "invalid" api_client.credentials(HTTP_AUTHORIZATION="Bearer " + token) expected_output = {"detail": _("Error decoding token.")} url = reverse("test-view") response = api_client.get(url) assert response.status_code == status.HTTP_401_UNAUTHORIZED assert response.json() == expected_output
def test_view_returns_401_for_corrupt_signature(api_client, user): payload = JSONWebTokenAuthentication.jwt_create_payload(user) token = JSONWebTokenAuthentication.jwt_encode_payload(payload) + "x" api_client.credentials(HTTP_AUTHORIZATION="Bearer " + token) expected_output = {"detail": _("Error decoding token.")} url = reverse("test-view") response = api_client.get(url) assert response.status_code == status.HTTP_401_UNAUTHORIZED assert response.json() == expected_output
def test_expired_token_returns_validation_error( user, call_auth_verify_endpoint ): payload = JSONWebTokenAuthentication.jwt_create_payload(user) payload["iat"] = 0 # beginning of time payload["exp"] = 1 # one second after beginning of time auth_token = JSONWebTokenAuthentication.jwt_encode_payload(payload) expected_output = {"non_field_errors": [_("Token has expired.")]} verify_response = call_auth_verify_endpoint(auth_token) assert verify_response.json() == expected_output
def test_view_returns_401_to_authorization_header_without_token(api_client): api_client.credentials(HTTP_AUTHORIZATION="Bearer ") expected_output = { "detail": _("Authentication credentials were not provided.") } url = reverse("test-view") response = api_client.get(url) assert response.status_code == status.HTTP_401_UNAUTHORIZED assert response.json() == expected_output
def test_token_without_username_returns_validation_error( user, call_auth_verify_endpoint ): payload = JSONWebTokenAuthentication.jwt_create_payload(user) payload.pop("username") auth_token = JSONWebTokenAuthentication.jwt_encode_payload(payload) expected_output = {"non_field_errors": [_("Invalid token.")]} verify_response = call_auth_verify_endpoint(auth_token) assert verify_response.json() == expected_output
def test_view_returns_401_for_invalid_algorithm(api_client, user): header = '{"alg": "bad", "typ": "JWT"}' token = base64.b64encode(header.encode('ascii')) + "..".encode('ascii') api_client.credentials(HTTP_AUTHORIZATION="Bearer " + token.decode()) expected_output = {"detail": _("Invalid token.")} url = reverse("test-view") response = api_client.get(url) assert response.status_code == status.HTTP_401_UNAUTHORIZED assert response.json() == expected_output
def test_token_without_required_token_id_returns_validation_error( monkeypatch, user, call_auth_verify_endpoint ): monkeypatch.setattr(api_settings, "JWT_TOKEN_ID", "require") payload = JSONWebTokenAuthentication.jwt_create_payload(user) payload.pop("jti") auth_token = JSONWebTokenAuthentication.jwt_encode_payload(payload) expected_output = {"non_field_errors": [_("Invalid token.")]} verify_response = call_auth_verify_endpoint(auth_token) assert verify_response.json() == expected_output
def check_payload(token): from rest_framework_jwt.authentication import JSONWebTokenAuthentication try: payload = JSONWebTokenAuthentication.jwt_decode_token(token) except jwt.ExpiredSignature: msg = _('Token has expired.') raise serializers.ValidationError(msg) except jwt.DecodeError: msg = _('Error decoding token.') raise serializers.ValidationError(msg) except jwt.InvalidTokenError: msg = _('Invalid token.') raise serializers.ValidationError(msg) if apps.is_installed('rest_framework_jwt.blacklist'): from rest_framework_jwt.blacklist.models import BlacklistedToken if BlacklistedToken.is_blocked(token, payload): msg = _('Token is blacklisted.') raise serializers.ValidationError(msg) return payload
def test_token_for_inactive_user_returns_validation_error( create_user, call_auth_verify_endpoint ): inactive_user = create_user( username="******", password="******", is_active=False ) payload = JSONWebTokenAuthentication.jwt_create_payload(inactive_user) auth_token = JSONWebTokenAuthentication.jwt_encode_payload(payload) expected_output = {"non_field_errors": [_("User account is disabled.")]} verify_response = call_auth_verify_endpoint(auth_token) assert verify_response.json() == expected_output
def test_without_orig_iat_in_payload__returns_validation_error( call_auth_refresh_endpoint, user): # create token without orig_iat in payload payload = JSONWebTokenAuthentication.jwt_create_payload(user) del payload["orig_iat"] auth_token = JSONWebTokenAuthentication.jwt_encode_payload(payload) expected_output = { "non_field_errors": [_("orig_iat field not found in token.")] } response = call_auth_refresh_endpoint(auth_token) assert response.json() == expected_output
class IsNotBlacklisted(BasePermission): message = _('You have been blacklisted.') def has_permission(self, request, view): token = JSONWebTokenAuthentication.get_token_from_request(request) # Don't check the blacklist for requests with no token. if token is None: return True # The token should already be validated before we call this. payload = jwt_decode(token, None, verify=False) return not BlacklistedToken.is_blocked(token, payload)
def authenticate_credentials(self, payload): """ Returns an active user that matches the payload's user id and email. """ username = self.jwt_get_username_from_payload(payload) if not username: msg = _('Invalid payload.') raise exceptions.AuthenticationFailed(msg) try: User = get_user_model() user = User.objects.get_by_natural_key(username) except User.DoesNotExist: msg = _('Invalid token.') raise exceptions.AuthenticationFailed(msg) if not user.is_active: msg = _('User account is disabled.') raise exceptions.AuthenticationFailed(msg) return user
def test_view_returns_401_when_username_does_not_exist(api_client, user): payload = JSONWebTokenAuthentication.jwt_create_payload(user) payload["username"] = "******" token = JSONWebTokenAuthentication.jwt_encode_payload(payload) api_client.credentials(HTTP_AUTHORIZATION="Bearer " + token) expected_output = {"detail": _("Invalid token.")} url = reverse("test-view") response = api_client.get(url) assert response.status_code == status.HTTP_401_UNAUTHORIZED assert response.json() == expected_output
def test_view_returns_401_for_bogus_issued_at(api_client, user): payload = JSONWebTokenAuthentication.jwt_create_payload(user) payload["iat"] = 'banana' token = JSONWebTokenAuthentication.jwt_encode_payload(payload) api_client.credentials(HTTP_AUTHORIZATION="Bearer " + token) expected_output = {"detail": _("Invalid token.")} url = reverse("test-view") response = api_client.get(url) assert response.status_code == status.HTTP_401_UNAUTHORIZED assert response.json() == expected_output
def test_blacklisted_token__returns_validation_error( call_auth_refresh_endpoint, user): payload = JSONWebTokenAuthentication.jwt_create_payload(user) auth_token = JSONWebTokenAuthentication.jwt_encode_payload(payload) BlacklistedToken.objects.create( token=auth_token, user=user, expires_at=timezone.now() - timedelta(days=7), ) expected_output = {"non_field_errors": [_("Token is blacklisted.")]} refresh_response = call_auth_refresh_endpoint(auth_token) assert refresh_response.json() == expected_output
def test_view_returns_401_to_deactivated_user(api_client, user): payload = JSONWebTokenAuthentication.jwt_create_payload(user) token = JSONWebTokenAuthentication.jwt_encode_payload(payload) user.is_active = False user.save() api_client.credentials(HTTP_AUTHORIZATION="Bearer " + token) expected_output = {"detail": _("User account is disabled.")} url = reverse("test-view") response = api_client.get(url) assert response.status_code == status.HTTP_401_UNAUTHORIZED assert response.json() == expected_output
def test_view_returns_401_to_invalid_token_prefix(api_client, user): payload = JSONWebTokenAuthentication.jwt_create_payload(user) token = JSONWebTokenAuthentication.jwt_encode_payload(payload) api_client.credentials(HTTP_AUTHORIZATION="INVALID_PREFIX " + token) expected_output = { "detail": _("Authentication credentials were not provided.") } url = reverse("test-view") response = api_client.get(url) assert response.status_code == status.HTTP_401_UNAUTHORIZED assert response.json() == expected_output
def test_view_returns_401_for_unknown_required_key_id(monkeypatch, api_client, user): monkeypatch.setattr(api_settings, "JWT_INSIST_ON_KID", True) header = '{"alg": "HS256", "kid": "unknown-key-id", "typ": "JWT"}' token = base64.b64encode(header.encode('ascii')) + "..".encode('ascii') api_client.credentials(HTTP_AUTHORIZATION="Bearer " + token.decode()) expected_output = {"detail": _("Error decoding token.")} url = reverse("test-view") response = api_client.get(url) assert response.status_code == status.HTTP_401_UNAUTHORIZED assert response.json() == expected_output