diff --git a/CHANGELOG.md b/CHANGELOG.md
index e356163c556f261e9d99240c0ace8f053ccc5a8c..2bcdcf995fb9c79e4aeb1e6c5c74947ae092e298 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
 # Changelog
 
+## 0.0.5
+
+Added:
+
+- When authenticated, a non-database backed user object is associated with the request.
+
 ## 0.0.4
 
 Added:
diff --git a/README.md b/README.md
index 11b679f3897abfd7c2d984fb0811d3c3fc3d8941..df5c0c747f1b9bf44f12f98baf5e424df56e031b 100644
--- a/README.md
+++ b/README.md
@@ -58,7 +58,7 @@ to your restframework settings:
 REST_FRAMEWORK = {
     'DEFAULT_AUTHENTICATION_CLASSES': [
         # rely on the API Gateway to provide authentication details
-        'apigatewayauth.APIGatewayAuthentication'
+        'apigatewayauth.authentication.APIGatewayAuthentication'
     ],
 }
 ```
diff --git a/apigatewayauth/__init__.py b/apigatewayauth/__init__.py
index da487e602d2cdca9876cd7c8cd9fe32cb3e78f8b..d4a4149404bee442679bef8bc3f8a8a5b1383b29 100644
--- a/apigatewayauth/__init__.py
+++ b/apigatewayauth/__init__.py
@@ -1,4 +1,8 @@
-from .api_gateway_auth import APIGatewayAuthentication  # noqa: F401
-from .api_gateway_auth import APIGatewayAuthenticationDetails  # noqa: F401
+# This import needs to be removed but to do so breaks compatibility with
+# existing users. See comments in apigatewayauth.auth.APIGatewayAuthentication.
+from .authentication import (  # noqa: F401
+    APIGatewayAuthentication,
+    APIGatewayAuthenticationDetails,
+)
 
 default_app_config = "apigatewayauth.apps.APIGatewayAuthConfig"
diff --git a/apigatewayauth/api_gateway_auth.py b/apigatewayauth/authentication.py
similarity index 65%
rename from apigatewayauth/api_gateway_auth.py
rename to apigatewayauth/authentication.py
index 612bb3b12179bab27083f9aa5dc38eadefca57c2..2e323308fea18b048dcbd62b4c271e4cae5d2734 100644
--- a/apigatewayauth/api_gateway_auth.py
+++ b/apigatewayauth/authentication.py
@@ -31,6 +31,24 @@ class APIGatewayAuthentication(authentication.BaseAuthentication):
     """
 
     def authenticate(self, request: Request):
+        # This import is needed here because APIGatewayUser references
+        # AnonymousUser from django.contrib.auth which, in turn, means that
+        # applications have to be ready at import time.
+        #
+        # This file is imported from apigatewayauth/__init__.py and
+        # apigatewayauth is imported at application configure time. The net
+        # upshot is that we cannot import directly or indirectly from
+        # django.contrib.auth at the top of this file and have to do it here.
+        #
+        # We can't remove the imports at the top of apigatewayauth/__init__.py
+        # because we have users of this library which set the DRF default
+        # authentication class to "apigatewayauth.APIGatewayAuthentication".
+        #
+        # These users need to be fixed up to use
+        # "apigatewayauth.authentication.APIGatewayAuthentication" instead and
+        # then we can move this import back where it belongs.
+        from .user import APIGatewayUser
+
         if not request.META.get("HTTP_X_API_ORG_NAME", None):
             # bail early if we look like we're not being called by the API Gateway
             return None
@@ -51,6 +69,5 @@ class APIGatewayAuthentication(authentication.BaseAuthentication):
             app_id=request.META.get("HTTP_X_API_DEVELOPER_APP_ID", None),
             client_id=request.META.get("HTTP_X_API_OAUTH2_CLIENT_ID", None),
         )
-        # the first item in the tuple represents the 'user' which we don't have when we've
-        # used the API Gateway for authentication.
-        return None, auth
+        user = APIGatewayUser(auth)
+        return user, auth
diff --git a/apigatewayauth/permissions.py b/apigatewayauth/permissions.py
index 51f11773c0263f9daf5f3aa6f8161037d0e24a9b..b996e7e182b076cb85a6f01a3a743c3f9d1312f2 100644
--- a/apigatewayauth/permissions.py
+++ b/apigatewayauth/permissions.py
@@ -7,7 +7,7 @@ from rest_framework import permissions, request
 from ucamlookup.ibisclient import IbisException, PersonMethods
 from ucamlookup.utils import get_connection
 
-from .api_gateway_auth import APIGatewayAuthenticationDetails
+from .authentication import APIGatewayAuthenticationDetails
 from .permissions_spec import (
     get_groups_with_permission,
     get_permission_spec,
diff --git a/apigatewayauth/tests/test_apigateway_auth.py b/apigatewayauth/tests/test_apigateway_auth.py
index 7f186a8d4a58536d87de1d27ae9c0c1bbd92edbd..8a28b70fa16ce63d116f35e18edd6216a27d1df7 100644
--- a/apigatewayauth/tests/test_apigateway_auth.py
+++ b/apigatewayauth/tests/test_apigateway_auth.py
@@ -3,7 +3,7 @@ from identitylib.identifiers import Identifier, IdentifierSchemes
 from rest_framework.exceptions import AuthenticationFailed
 from rest_framework.test import APIRequestFactory
 
-from apigatewayauth.api_gateway_auth import (
+from apigatewayauth.authentication import (
     APIGatewayAuthentication,
     APIGatewayAuthenticationDetails,
 )
@@ -90,7 +90,7 @@ class APIGatewayAuthTestCase(TestCase):
                 }
             )
         )
-        self.assertIsNone(user)
+        self.assertEqual(user.id, str(Identifier("a123", IdentifierSchemes.CRSID)))
 
         self.assertEqual(
             auth,
@@ -102,6 +102,19 @@ class APIGatewayAuthTestCase(TestCase):
             ),
         )
 
+    def test_returns_authenticated_non_anonymous_user(self):
+        user, _ = self.auth.authenticate(
+            self.request_with_headers(
+                {
+                    "x-api-org-name": "test",
+                    "x-api-developer-app-class": "public",
+                    "x-api-oauth2-user": str(Identifier("a123", IdentifierSchemes.CRSID)),
+                }
+            )
+        )
+        self.assertFalse(user.is_anonymous)
+        self.assertTrue(user.is_authenticated)
+
     def test_will_pass_through_scopes(self):
         _, auth = self.auth.authenticate(
             self.request_with_headers(
diff --git a/apigatewayauth/tests/test_permissions.py b/apigatewayauth/tests/test_permissions.py
index b2abc6ef92f0efbdc735a85247974a43da45bab8..a1448618e934cb1ddc710a3972ff150022046b66 100644
--- a/apigatewayauth/tests/test_permissions.py
+++ b/apigatewayauth/tests/test_permissions.py
@@ -5,7 +5,7 @@ from identitylib.identifiers import Identifier, IdentifierSchemes
 from rest_framework.test import APIRequestFactory
 from ucamlookup.ibisclient import IbisException
 
-from apigatewayauth.api_gateway_auth import APIGatewayAuthenticationDetails
+from apigatewayauth.authentication import APIGatewayAuthenticationDetails
 from apigatewayauth.permissions import (
     Disallowed,
     HasAnyScope,
diff --git a/apigatewayauth/user.py b/apigatewayauth/user.py
new file mode 100644
index 0000000000000000000000000000000000000000..086bc7a596a41bfda9923eb361be3a1fcd830e4a
--- /dev/null
+++ b/apigatewayauth/user.py
@@ -0,0 +1,26 @@
+from django.contrib.auth.models import AnonymousUser
+
+from .authentication import APIGatewayAuthenticationDetails
+
+
+class APIGatewayUser(AnonymousUser):
+    """
+    A Django user representing the authenticated principal. This user is not
+    backed by a database object and so they can have no permissions in the
+    Django sense.
+    """
+
+    def __init__(self, auth: APIGatewayAuthenticationDetails):
+        super().__init__()
+        self.username = self.id = self.pk = str(auth.principal_identifier)
+
+    @property
+    def is_anonymous(self):
+        return False
+
+    @property
+    def is_authenticated(self):
+        return True
+
+    def __str__(self):
+        return self.username
diff --git a/runtests.py b/runtests.py
index ed0ac74cc0984fe297d0469b2d045d94fe71465c..ab0a8f7fcc736b9afc194ff926f993b9f6c9342a 100644
--- a/runtests.py
+++ b/runtests.py
@@ -20,7 +20,12 @@ settings.configure(
     },
     TIME_ZONE="Europe/London",
     USE_TZ=True,
-    INSTALLED_APPS=("apigatewayauth", "apigatewayauth.tests.mocks"),
+    INSTALLED_APPS=(
+        "django.contrib.contenttypes",
+        "django.contrib.auth",
+        "apigatewayauth",
+        "apigatewayauth.tests.mocks",
+    ),
     MIDDLEWARE_CLASSES=(),
     MIDDLEWARE=(),
     TEMPLATES=[],