diff --git a/authentication/tests/test_logout_views.py b/authentication/tests/test_logout_views.py
new file mode 100644
index 0000000000000000000000000000000000000000..bc5b3a66db213590387dcb9a54915314047ed66c
--- /dev/null
+++ b/authentication/tests/test_logout_views.py
@@ -0,0 +1,48 @@
+import random
+
+import pytest
+from django.urls import reverse
+from rest_framework import status
+
+from authentication.models import AuthToken
+from authentication.tests.factories import AuthTokenFactory
+
+
+def assert_is_unauthorized(response):
+    assert response.status_code == status.HTTP_401_UNAUTHORIZED
+
+
+@pytest.mark.parametrize(
+    "viewname,revoked_all", [("knox_logout", False), ("knox_logoutall", True)]
+)
+def test_logout(authenticated_api_client, account, viewname, revoked_all):
+    n_tokens = random.randrange(10, 21)
+    AuthTokenFactory.create_batch(n_tokens, user=account)
+
+    # Also create some tokens that are not for this user
+    n_other_tokens = random.randrange(10, 21)
+    AuthTokenFactory.create_batch(n_other_tokens)
+
+    # authenticated_api_client also created an authentication token for the user
+    assert account.auth_token_set.count() == n_tokens + 1
+
+    url = reverse(viewname)
+
+    response = authenticated_api_client.post(url)
+
+    assert response.status_code == status.HTTP_204_NO_CONTENT
+
+    expected_count = 0 if revoked_all else n_tokens
+    assert account.auth_token_set.count() == expected_count
+
+    # Tokens from other users should not be touched
+    assert AuthToken.objects.exclude(user=account).count() == n_other_tokens
+
+    # We are unauthorised now, so trying to logout again will fail even while we provide an access
+    # token.
+    assert_is_unauthorized(authenticated_api_client.post(url))
+
+
+@pytest.mark.parametrize("viewname", ["knox_logout", "knox_logoutall"])
+def test_logout_unauthenticated(api_client, viewname):
+    assert_is_unauthorized(api_client.post(reverse(viewname)))
diff --git a/authentication/urls.py b/authentication/urls.py
index fb222f0ec5d39dd4a668a558f4fe01a3cd8a12d3..47b320c8b2d67c73d15c1c8ce9d40c1f85fe7cfa 100644
--- a/authentication/urls.py
+++ b/authentication/urls.py
@@ -1,14 +1,9 @@
 from django.urls import path
-from knox import views as knox_views
 
-from .views import LoginView
+from .views import LoginView, LogoutAllView, LogoutView
 
 urlpatterns = [
     path("", LoginView.as_view(), name="knox_login"),
-    path("revoke/", knox_views.LogoutView.as_view(versioning_class=None), name="knox_logout"),
-    path(
-        "revoke/all/",
-        knox_views.LogoutAllView.as_view(versioning_class=None),
-        name="knox_logoutall",
-    ),
+    path("revoke/", LogoutView.as_view(), name="knox_logout"),
+    path("revoke/all/", LogoutAllView.as_view(), name="knox_logoutall"),
 ]
diff --git a/authentication/views.py b/authentication/views.py
index c1d40bf376e9091a9301f55a27780a5f55b82bc8..bc7c61a1c112f738541ee0cd6144f8a19d8de4a1 100644
--- a/authentication/views.py
+++ b/authentication/views.py
@@ -1,5 +1,5 @@
 from knox.views import LoginView as KnoxLoginView
-from rest_framework import parsers, renderers, serializers, status
+from rest_framework import parsers, renderers, serializers, status, views
 from rest_framework.response import Response
 
 from authentication.errors import OAuth2Error
@@ -68,3 +68,29 @@ class LoginView(KnoxLoginView):
             return default_handler(exc, context)
 
         return exception_handler
+
+
+class LogoutView(views.APIView):
+    throttle_classes = ()
+    versioning_class = None
+    renderer_classes = (renderers.JSONRenderer,)
+
+    def get_post_response(self, request):
+        return Response(None, status=status.HTTP_204_NO_CONTENT)
+
+    def post(self, request, format=None):
+        request._auth.delete()
+        return self.get_post_response(request)
+
+
+class LogoutAllView(views.APIView):
+    throttle_classes = ()
+    versioning_class = None
+    renderer_classes = (renderers.JSONRenderer,)
+
+    def get_post_response(self, request):
+        return Response(None, status=status.HTTP_204_NO_CONTENT)
+
+    def post(self, request, format=None):
+        request.user.auth_token_set.all().delete()
+        return self.get_post_response(request)