diff --git a/.env.dev b/.env.dev
index e695ed78556024dd4800b7892806fe16221fb97e..4cad27e21154ffc7ad4b55a28cefe30b3fab8ed3 100644
--- a/.env.dev
+++ b/.env.dev
@@ -3,7 +3,7 @@ DJANGO_SETTINGS_MODULE=config.settings.local
 DJANGO_SECRET_KEY=dev
 C_FORCE_ROOT=true
 FUNKWHALE_HOSTNAME=localhost
-FUNKWHALE_PROTOCOL=http
+FUNKWHALE_PROTOCOL=https
 PYTHONDONTWRITEBYTECODE=true
 VUE_PORT=8080
 MUSIC_DIRECTORY_PATH=/music
diff --git a/api/config/api_urls.py b/api/config/api_urls.py
index 04fbda87c7c958ee7dab011d52078e73f03db573..bbfadbd4ead628fd99bcea4530192b4b9960bb1a 100644
--- a/api/config/api_urls.py
+++ b/api/config/api_urls.py
@@ -10,7 +10,6 @@ from funkwhale_api.music import views
 from funkwhale_api.playlists import views as playlists_views
 from funkwhale_api.subsonic.views import SubsonicViewSet
 from funkwhale_api.tags import views as tags_views
-from funkwhale_api.users import jwt_views
 
 router = common_routers.OptionalSlashRouter()
 router.register(r"activity", activity_views.ActivityViewSet, "activity")
@@ -84,8 +83,6 @@ v1_patterns += [
         r"^oauth/",
         include(("funkwhale_api.users.oauth.urls", "oauth"), namespace="oauth"),
     ),
-    url(r"^token/?$", jwt_views.obtain_jwt_token, name="token"),
-    url(r"^token/refresh/?$", jwt_views.refresh_jwt_token, name="token_refresh"),
     url(r"^rate-limit/?$", common_views.RateLimitView.as_view(), name="rate-limit"),
     url(
         r"^text-preview/?$", common_views.TextPreviewView.as_view(), name="text-preview"
diff --git a/api/config/settings/common.py b/api/config/settings/common.py
index 2f207289ce825b4eb1cfe4e72e6e2c2afa611538..9876db017195ccfd72601cb9c4a22dc20af610c8 100644
--- a/api/config/settings/common.py
+++ b/api/config/settings/common.py
@@ -2,7 +2,6 @@
 from __future__ import absolute_import, unicode_literals
 
 from collections import OrderedDict
-import datetime
 import logging.config
 import sys
 
@@ -812,13 +811,6 @@ def get_user_secret_key(user):
     return settings.SECRET_KEY + str(user.secret_key)
 
 
-JWT_AUTH = {
-    "JWT_ALLOW_REFRESH": True,
-    "JWT_EXPIRATION_DELTA": datetime.timedelta(days=7),
-    "JWT_REFRESH_EXPIRATION_DELTA": datetime.timedelta(days=30),
-    "JWT_AUTH_HEADER_PREFIX": "JWT",
-    "JWT_GET_USER_SECRET_KEY": get_user_secret_key,
-}
 OLD_PASSWORD_FIELD_ENABLED = True
 AUTH_PASSWORD_VALIDATORS = [
     {
@@ -857,9 +849,6 @@ REST_FRAMEWORK = {
     "DEFAULT_AUTHENTICATION_CLASSES": (
         "funkwhale_api.common.authentication.OAuth2Authentication",
         "funkwhale_api.common.authentication.ApplicationTokenAuthentication",
-        "funkwhale_api.common.authentication.JSONWebTokenAuthenticationQS",
-        "funkwhale_api.common.authentication.BearerTokenHeaderAuth",
-        "funkwhale_api.common.authentication.JSONWebTokenAuthentication",
         "rest_framework.authentication.BasicAuthentication",
         "rest_framework.authentication.SessionAuthentication",
     ),
@@ -998,14 +987,6 @@ THROTTLING_RATES = {
         "rate": THROTTLING_USER_RATES.get("login", "30/hour"),
         "description": "Login",
     },
-    "jwt-login": {
-        "rate": THROTTLING_USER_RATES.get("jwt-login", "30/hour"),
-        "description": "JWT token creation",
-    },
-    "jwt-refresh": {
-        "rate": THROTTLING_USER_RATES.get("jwt-refresh", "30/hour"),
-        "description": "JWT token refresh",
-    },
     "signup": {
         "rate": THROTTLING_USER_RATES.get("signup", "10/day"),
         "description": "Account creation",
@@ -1052,7 +1033,6 @@ REST_AUTH_SERIALIZERS = {
     "PASSWORD_RESET_SERIALIZER": "funkwhale_api.users.serializers.PasswordResetSerializer"  # noqa
 }
 REST_SESSION_LOGIN = False
-REST_USE_JWT = True
 
 ATOMIC_REQUESTS = False
 USE_X_FORWARDED_HOST = True
diff --git a/api/funkwhale_api/common/authentication.py b/api/funkwhale_api/common/authentication.py
index 11447ce23ace35646c2ab8085307e6cd9f04aef3..1340aed91729cc3343a347294477d9a54aa8bec8 100644
--- a/api/funkwhale_api/common/authentication.py
+++ b/api/funkwhale_api/common/authentication.py
@@ -1,5 +1,4 @@
 from django.conf import settings
-from django.utils.encoding import smart_text
 from django.utils.translation import ugettext as _
 
 from django.core.cache import cache
@@ -9,8 +8,6 @@ from oauth2_provider.contrib.rest_framework.authentication import (
     OAuth2Authentication as BaseOAuth2Authentication,
 )
 from rest_framework import exceptions
-from rest_framework_jwt import authentication
-from rest_framework_jwt.settings import api_settings
 
 from funkwhale_api.users import models as users_models
 
@@ -76,116 +73,3 @@ class ApplicationTokenAuthentication(object):
 
         request.scopes = application.scope.split()
         return user, None
-
-
-class BaseJsonWebTokenAuth(object):
-    def authenticate(self, request):
-        try:
-            return super().authenticate(request)
-        except UnverifiedEmail as e:
-            msg = _("You need to verify your email address.")
-            resend_confirmation_email(request, e.user)
-            raise exceptions.AuthenticationFailed(msg)
-
-    def authenticate_credentials(self, payload):
-        """
-        We have to implement this method by hand to ensure we can check that the
-        User has a verified email, if required
-        """
-        User = authentication.get_user_model()
-        username = authentication.jwt_get_username_from_payload(payload)
-
-        if not username:
-            msg = _("Invalid payload.")
-            raise exceptions.AuthenticationFailed(msg)
-
-        try:
-            user = User.objects.get_by_natural_key(username)
-        except User.DoesNotExist:
-            msg = _("Invalid signature.")
-            raise exceptions.AuthenticationFailed(msg)
-
-        if not user.is_active:
-            msg = _("User account is disabled.")
-            raise exceptions.AuthenticationFailed(msg)
-
-        if should_verify_email(user):
-            raise UnverifiedEmail(user)
-
-        return user
-
-
-class JSONWebTokenAuthenticationQS(
-    BaseJsonWebTokenAuth, authentication.BaseJSONWebTokenAuthentication
-):
-
-    www_authenticate_realm = "api"
-
-    def get_jwt_value(self, request):
-        token = request.query_params.get("jwt")
-        if "jwt" in request.query_params and not token:
-            msg = _("Invalid Authorization header. No credentials provided.")
-            raise exceptions.AuthenticationFailed(msg)
-        return token
-
-    def authenticate_header(self, request):
-        return '{0} realm="{1}"'.format(
-            api_settings.JWT_AUTH_HEADER_PREFIX, self.www_authenticate_realm
-        )
-
-
-class BearerTokenHeaderAuth(
-    BaseJsonWebTokenAuth, authentication.BaseJSONWebTokenAuthentication
-):
-    """
-    For backward compatibility purpose, we used Authorization: JWT <token>
-    but Authorization: Bearer <token> is probably better.
-    """
-
-    www_authenticate_realm = "api"
-
-    def get_jwt_value(self, request):
-        auth = authentication.get_authorization_header(request).split()
-        auth_header_prefix = "bearer"
-
-        if not auth:
-            if api_settings.JWT_AUTH_COOKIE:
-                return request.COOKIES.get(api_settings.JWT_AUTH_COOKIE)
-            return None
-
-        if smart_text(auth[0].lower()) != auth_header_prefix:
-            return None
-
-        if len(auth) == 1:
-            msg = _("Invalid Authorization header. No credentials provided.")
-            raise exceptions.AuthenticationFailed(msg)
-        elif len(auth) > 2:
-            msg = _(
-                "Invalid Authorization header. Credentials string "
-                "should not contain spaces."
-            )
-            raise exceptions.AuthenticationFailed(msg)
-
-        return auth[1]
-
-    def authenticate_header(self, request):
-        return '{0} realm="{1}"'.format("Bearer", self.www_authenticate_realm)
-
-    def authenticate(self, request):
-        auth = super().authenticate(request)
-        if auth:
-            if not auth[0].actor:
-                auth[0].create_actor()
-        return auth
-
-
-class JSONWebTokenAuthentication(
-    BaseJsonWebTokenAuth, authentication.JSONWebTokenAuthentication
-):
-    def authenticate(self, request):
-        auth = super().authenticate(request)
-
-        if auth:
-            if not auth[0].actor:
-                auth[0].create_actor()
-        return auth
diff --git a/api/funkwhale_api/users/jwt_views.py b/api/funkwhale_api/users/jwt_views.py
deleted file mode 100644
index 7d797a9b9d74bf901fd6d8b3d9cade63fd65a288..0000000000000000000000000000000000000000
--- a/api/funkwhale_api/users/jwt_views.py
+++ /dev/null
@@ -1,18 +0,0 @@
-from rest_framework_jwt import views as jwt_views
-
-from . import serializers
-
-
-class ObtainJSONWebToken(jwt_views.ObtainJSONWebToken):
-    throttling_scopes = {"*": {"anonymous": "jwt-login", "authenticated": "jwt-login"}}
-    serializer_class = serializers.JSONWebTokenSerializer
-
-
-class RefreshJSONWebToken(jwt_views.RefreshJSONWebToken):
-    throttling_scopes = {
-        "*": {"anonymous": "jwt-refresh", "authenticated": "jwt-refresh"}
-    }
-
-
-obtain_jwt_token = ObtainJSONWebToken.as_view()
-refresh_jwt_token = RefreshJSONWebToken.as_view()
diff --git a/api/funkwhale_api/users/serializers.py b/api/funkwhale_api/users/serializers.py
index 58c16ac1a88db237409c5299d86e70fe70f15265..72bc5833fb00a804147f35ca2db367918459db88 100644
--- a/api/funkwhale_api/users/serializers.py
+++ b/api/funkwhale_api/users/serializers.py
@@ -10,10 +10,8 @@ from allauth.account import models as allauth_models
 from rest_auth.serializers import PasswordResetSerializer as PRS
 from rest_auth.registration.serializers import RegisterSerializer as RS, get_adapter
 from rest_framework import serializers
-from rest_framework_jwt import serializers as jwt_serializers
 
 from funkwhale_api.activity import serializers as activity_serializers
-from funkwhale_api.common import authentication
 from funkwhale_api.common import models as common_models
 from funkwhale_api.common import preferences
 from funkwhale_api.common import serializers as common_serializers
@@ -42,15 +40,6 @@ username_validators = [ASCIIUsernameValidator()]
 NOOP = object()
 
 
-class JSONWebTokenSerializer(jwt_serializers.JSONWebTokenSerializer):
-    def validate(self, data):
-        try:
-            return super().validate(data)
-        except authentication.UnverifiedEmail as e:
-            authentication.send_email_confirmation(self.context["request"], e.user)
-            raise serializers.ValidationError("Please verify your email address.")
-
-
 class RegisterSerializer(RS):
     invitation = serializers.CharField(
         required=False, allow_null=True, allow_blank=True
diff --git a/api/requirements/base.txt b/api/requirements/base.txt
index 309335869db61aedd76ae8aa40cee30787f2195c..9048ee198f5081eddd83632c86e464cd47918848 100644
--- a/api/requirements/base.txt
+++ b/api/requirements/base.txt
@@ -25,7 +25,6 @@ celery~=4.4.0
 django-cors-headers~=3.4.0
 musicbrainzngs~=0.7.1
 djangorestframework~=3.11.0
-djangorestframework-jwt~=1.11.0
 arrow~=0.15.5
 persisting-theory~=0.2.0
 django-versatileimagefield~=2.0.0
diff --git a/api/tests/common/test_authentication.py b/api/tests/common/test_authentication.py
index 5678abcf6a89517a735aef1e473a91d061db6758..ccd39fc5a67f063d56c1288c2ddc07d9cbb2d2ba 100644
--- a/api/tests/common/test_authentication.py
+++ b/api/tests/common/test_authentication.py
@@ -1,8 +1,5 @@
 import pytest
 
-from rest_framework import exceptions
-from rest_framework_jwt.settings import api_settings as jwt_settings
-
 from funkwhale_api.common import authentication
 
 
@@ -33,35 +30,6 @@ def test_should_verify_email(
     assert authentication.should_verify_email(user) is expected
 
 
-@pytest.mark.parametrize(
-    "setting_value, verified_email, expected",
-    [
-        ("mandatory", False, True),
-        ("optional", False, False),
-        ("mandatory", True, False),
-        ("optional", True, False),
-    ],
-)
-def test_json_webtoken_auth_verify_email_validity(
-    setting_value, verified_email, expected, factories, settings, mocker, api_request
-):
-    settings.ACCOUNT_EMAIL_VERIFICATION = setting_value
-    user = factories["users.User"](verified_email=verified_email)
-    should_verify = mocker.spy(authentication, "should_verify_email")
-    payload = jwt_settings.JWT_PAYLOAD_HANDLER(user)
-    token = jwt_settings.JWT_ENCODE_HANDLER(payload)
-    request = api_request.get("/", HTTP_AUTHORIZATION="JWT {}".format(token))
-
-    auth = authentication.JSONWebTokenAuthentication()
-    if expected is False:
-        assert auth.authenticate(request)[0] == user
-    else:
-        with pytest.raises(exceptions.AuthenticationFailed, match=r".*verify.*"):
-            auth.authenticate(request)
-
-    should_verify.assert_called_once_with(user)
-
-
 def test_app_token_authentication(factories, api_request):
     user = factories["users.User"]()
     app = factories["users.Application"](user=user, scope="read write")
diff --git a/api/tests/common/test_routers.py b/api/tests/common/test_routers.py
index 5d1710f09f276aaf19e798fdff39bddd22d781d6..fa371131818fe3aff89faf087718175428a28b2c 100644
--- a/api/tests/common/test_routers.py
+++ b/api/tests/common/test_routers.py
@@ -22,8 +22,6 @@ from django import urls
         "/api/v1/manage/accounts",
         "/api/v1/oauth/apps",
         "/api/v1/moderation/content-filters",
-        "/api/v1/token",
-        "/api/v1/token/refresh",
         "/api/v1/instance/settings",
         "/api/v1/instance/nodeinfo/2.0",
     ],
diff --git a/api/tests/test_auth.py b/api/tests/test_auth.py
index 653110f0944e0b08e13bf40f97460de4d47a83da..9758a0f376d5c89cb7d06464a1b8767eb220a605 100644
--- a/api/tests/test_auth.py
+++ b/api/tests/test_auth.py
@@ -5,20 +5,6 @@ jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
 jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
 
 
-def test_can_authenticate_using_jwt_token_param_in_url(factories, preferences, client):
-    user = factories["users.User"]()
-    preferences["common__api_authentication_required"] = True
-    url = reverse("api:v1:tracks-list")
-    response = client.get(url)
-
-    assert response.status_code == 401
-
-    payload = jwt_payload_handler(user)
-    token = jwt_encode_handler(payload)
-    response = client.get(url, data={"jwt": token})
-    assert response.status_code == 200
-
-
 def test_can_authenticate_using_oauth_token_param_in_url(
     factories, preferences, client, mocker
 ):
diff --git a/api/tests/users/test_jwt.py b/api/tests/users/test_jwt.py
deleted file mode 100644
index d0fe1a1fa70a0ddb5736eb95fdda7178ecb77c60..0000000000000000000000000000000000000000
--- a/api/tests/users/test_jwt.py
+++ /dev/null
@@ -1,43 +0,0 @@
-import pytest
-from jwt.exceptions import DecodeError
-from rest_framework_jwt.settings import api_settings
-
-
-def test_can_invalidate_token_when_changing_user_secret_key(factories):
-    user = factories["users.User"]()
-    u1 = user.secret_key
-    jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
-    jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
-    payload = jwt_payload_handler(user)
-    payload = jwt_encode_handler(payload)
-
-    # this should work
-    api_settings.JWT_DECODE_HANDLER(payload)
-
-    # now we update the secret key
-    user.update_secret_key()
-    user.save()
-    assert user.secret_key != u1
-
-    # token should be invalid
-    with pytest.raises(DecodeError):
-        api_settings.JWT_DECODE_HANDLER(payload)
-
-
-def test_can_invalidate_token_when_changing_settings_secret_key(factories, settings):
-    settings.SECRET_KEY = "test1"
-    user = factories["users.User"]()
-    jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
-    jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
-    payload = jwt_payload_handler(user)
-    payload = jwt_encode_handler(payload)
-
-    # this should work
-    api_settings.JWT_DECODE_HANDLER(payload)
-
-    # now we update the secret key
-    settings.SECRET_KEY = "test2"
-
-    # token should be invalid
-    with pytest.raises(DecodeError):
-        api_settings.JWT_DECODE_HANDLER(payload)
diff --git a/api/tests/users/test_views.py b/api/tests/users/test_views.py
index 9b30c78bb4c0301daa526610e73ccb02885bf831..52b599187b2ef01b99a80180180aa0e7eac1ddba 100644
--- a/api/tests/users/test_views.py
+++ b/api/tests/users/test_views.py
@@ -134,42 +134,6 @@ def test_can_fetch_data_from_api(api_client, factories):
     )
 
 
-def test_can_get_token_via_api(api_client, factories):
-    user = factories["users.User"]()
-    url = reverse("api:v1:token")
-    payload = {"username": user.username, "password": "test"}
-
-    response = api_client.post(url, payload)
-    assert response.status_code == 200
-    assert "token" in response.data
-
-
-def test_can_get_token_via_api_inactive(api_client, factories):
-    user = factories["users.User"](is_active=False)
-    url = reverse("api:v1:token")
-    payload = {"username": user.username, "password": "test"}
-
-    response = api_client.post(url, payload)
-    assert response.status_code == 400
-
-
-def test_can_refresh_token_via_api(api_client, factories, mocker):
-    # first, we get a token
-    user = factories["users.User"]()
-    url = reverse("api:v1:token")
-    payload = {"username": user.username, "password": "test"}
-
-    response = api_client.post(url, payload)
-    assert response.status_code == 200
-
-    token = response.data["token"]
-    url = reverse("api:v1:token_refresh")
-    response = api_client.post(url, {"token": token})
-
-    assert response.status_code == 200
-    assert "token" in response.data
-
-
 def test_changing_password_updates_secret_key(logged_in_api_client):
     user = logged_in_api_client.user
     password = user.password
@@ -488,40 +452,6 @@ def test_signup_with_approval_enabled_validation_error(
     assert response.status_code == 400
 
 
-def test_user_login_jwt(factories, api_client):
-    user = factories["users.User"]()
-    data = {
-        "username": user.username,
-        "password": "test",
-    }
-    url = reverse("api:v1:token")
-    response = api_client.post(url, data)
-    assert response.status_code == 200
-
-
-@pytest.mark.parametrize(
-    "setting_value, verified_email, expected_status_code",
-    [
-        ("mandatory", False, 400),
-        ("mandatory", True, 200),
-        ("optional", False, 200),
-        ("optional", True, 200),
-    ],
-)
-def test_user_login_jwt_honor_email_verification(
-    setting_value, verified_email, expected_status_code, settings, factories, api_client
-):
-    settings.ACCOUNT_EMAIL_VERIFICATION = setting_value
-    user = factories["users.User"](verified_email=verified_email)
-    data = {
-        "username": user.username,
-        "password": "test",
-    }
-    url = reverse("api:v1:token")
-    response = api_client.post(url, data)
-    assert response.status_code == expected_status_code
-
-
 def test_login_via_api(api_client, factories):
     user = factories["users.User"]()
     url = reverse("api:v1:users:login")
diff --git a/changes/changelog.d/1108.enhancement b/changes/changelog.d/1108.enhancement
new file mode 100644
index 0000000000000000000000000000000000000000..96e7b9e6dfe367cec62ef30aef7c37c0767fd17a
--- /dev/null
+++ b/changes/changelog.d/1108.enhancement
@@ -0,0 +1 @@
+Remove deprecated JWT Authentication (#1108) (1108)