diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py
index de4de7cdfd..7d62810923 100644
--- a/django/contrib/auth/models.py
+++ b/django/contrib/auth/models.py
@@ -195,38 +195,6 @@ class UserManager(BaseUserManager):
         return u
 
 
-# A few helper functions for common logic between User and AnonymousUser.
-def _user_get_all_permissions(user, obj):
-    permissions = set()
-    for backend in auth.get_backends():
-        if hasattr(backend, "get_all_permissions"):
-            if obj is not None:
-                permissions.update(backend.get_all_permissions(user, obj))
-            else:
-                permissions.update(backend.get_all_permissions(user))
-    return permissions
-
-
-def _user_has_perm(user, perm, obj):
-    for backend in auth.get_backends():
-        if hasattr(backend, "has_perm"):
-            if obj is not None:
-                if backend.has_perm(user, perm, obj):
-                    return True
-            else:
-                if backend.has_perm(user, perm):
-                    return True
-    return False
-
-
-def _user_has_module_perms(user, app_label):
-    for backend in auth.get_backends():
-        if hasattr(backend, "has_module_perms"):
-            if backend.has_module_perms(user, app_label):
-                return True
-    return False
-
-
 @python_2_unicode_compatible
 class AbstractBaseUser(models.Model):
     password = models.CharField(_('password'), max_length=128)
@@ -290,32 +258,46 @@ class AbstractBaseUser(models.Model):
         raise NotImplementedError()
 
 
-class AbstractUser(AbstractBaseUser):
-    """
-    An abstract base class implementing a fully featured User model with
-    admin-compliant permissions.
+# A few helper functions for common logic between User and AnonymousUser.
+def _user_get_all_permissions(user, obj):
+    permissions = set()
+    for backend in auth.get_backends():
+        if hasattr(backend, "get_all_permissions"):
+            if obj is not None:
+                permissions.update(backend.get_all_permissions(user, obj))
+            else:
+                permissions.update(backend.get_all_permissions(user))
+    return permissions
 
-    Username, password and email are required. Other fields are optional.
+
+def _user_has_perm(user, perm, obj):
+    for backend in auth.get_backends():
+        if hasattr(backend, "has_perm"):
+            if obj is not None:
+                if backend.has_perm(user, perm, obj):
+                    return True
+            else:
+                if backend.has_perm(user, perm):
+                    return True
+    return False
+
+
+def _user_has_module_perms(user, app_label):
+    for backend in auth.get_backends():
+        if hasattr(backend, "has_module_perms"):
+            if backend.has_module_perms(user, app_label):
+                return True
+    return False
+
+
+class PermissionsMixin(models.Model):
+    """
+    A mixin class that adds the fields and methods necessary to support
+    Django's Group and Permission model using the ModelBackend.
     """
-    username = models.CharField(_('username'), max_length=30, unique=True,
-        help_text=_('Required. 30 characters or fewer. Letters, numbers and '
-                    '@/./+/-/_ characters'),
-        validators=[
-            validators.RegexValidator(re.compile('^[\w.@+-]+$'), _('Enter a valid username.'), 'invalid')
-        ])
-    first_name = models.CharField(_('first name'), max_length=30, blank=True)
-    last_name = models.CharField(_('last name'), max_length=30, blank=True)
-    email = models.EmailField(_('email address'), blank=True)
-    is_staff = models.BooleanField(_('staff status'), default=False,
-        help_text=_('Designates whether the user can log into this admin '
-                    'site.'))
-    is_active = models.BooleanField(_('active'), default=True,
-        help_text=_('Designates whether this user should be treated as '
-                    'active. Unselect this instead of deleting accounts.'))
     is_superuser = models.BooleanField(_('superuser status'), default=False,
         help_text=_('Designates that this user has all permissions without '
                     'explicitly assigning them.'))
-    date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
     groups = models.ManyToManyField(Group, verbose_name=_('groups'),
         blank=True, help_text=_('The groups this user belongs to. A user will '
                                 'get all permissions granted to each of '
@@ -324,30 +306,9 @@ class AbstractUser(AbstractBaseUser):
         verbose_name=_('user permissions'), blank=True,
         help_text='Specific permissions for this user.')
 
-    objects = UserManager()
-
-    USERNAME_FIELD = 'username'
-    REQUIRED_FIELDS = ['email']
-
     class Meta:
-        verbose_name = _('user')
-        verbose_name_plural = _('users')
         abstract = True
 
-    def get_absolute_url(self):
-        return "/users/%s/" % urlquote(self.username)
-
-    def get_full_name(self):
-        """
-        Returns the first_name plus the last_name, with a space in between.
-        """
-        full_name = '%s %s' % (self.first_name, self.last_name)
-        return full_name.strip()
-
-    def get_short_name(self):
-        "Returns the short name for the user."
-        return self.first_name
-
     def get_group_permissions(self, obj=None):
         """
         Returns a list of permission strings that this user has through his/her
@@ -405,6 +366,55 @@ class AbstractUser(AbstractBaseUser):
 
         return _user_has_module_perms(self, app_label)
 
+
+class AbstractUser(AbstractBaseUser, PermissionsMixin):
+    """
+    An abstract base class implementing a fully featured User model with
+    admin-compliant permissions.
+
+    Username, password and email are required. Other fields are optional.
+    """
+    username = models.CharField(_('username'), max_length=30, unique=True,
+        help_text=_('Required. 30 characters or fewer. Letters, numbers and '
+                    '@/./+/-/_ characters'),
+        validators=[
+            validators.RegexValidator(re.compile('^[\w.@+-]+$'), _('Enter a valid username.'), 'invalid')
+        ])
+    first_name = models.CharField(_('first name'), max_length=30, blank=True)
+    last_name = models.CharField(_('last name'), max_length=30, blank=True)
+    email = models.EmailField(_('email address'), blank=True)
+    is_staff = models.BooleanField(_('staff status'), default=False,
+        help_text=_('Designates whether the user can log into this admin '
+                    'site.'))
+    is_active = models.BooleanField(_('active'), default=True,
+        help_text=_('Designates whether this user should be treated as '
+                    'active. Unselect this instead of deleting accounts.'))
+    date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
+
+    objects = UserManager()
+
+    USERNAME_FIELD = 'username'
+    REQUIRED_FIELDS = ['email']
+
+    class Meta:
+        verbose_name = _('user')
+        verbose_name_plural = _('users')
+        abstract = True
+
+    def get_absolute_url(self):
+        return "/users/%s/" % urlquote(self.username)
+
+    def get_full_name(self):
+        """
+        Returns the first_name plus the last_name, with a space in between.
+        """
+        full_name = '%s %s' % (self.first_name, self.last_name)
+        return full_name.strip()
+
+    def get_short_name(self):
+        "Returns the short name for the user."
+        return self.first_name
+
     def email_user(self, subject, message, from_email=None):
         """
         Sends an email to this User.
diff --git a/django/contrib/auth/tests/auth_backends.py b/django/contrib/auth/tests/auth_backends.py
index e92f159ff9..094770889a 100644
--- a/django/contrib/auth/tests/auth_backends.py
+++ b/django/contrib/auth/tests/auth_backends.py
@@ -4,7 +4,7 @@ from datetime import date
 from django.conf import settings
 from django.contrib.auth.models import User, Group, Permission, AnonymousUser
 from django.contrib.auth.tests.utils import skipIfCustomUser
-from django.contrib.auth.tests.custom_user import ExtensionUser
+from django.contrib.auth.tests.custom_user import ExtensionUser, CustomPermissionsUser
 from django.contrib.contenttypes.models import ContentType
 from django.core.exceptions import ImproperlyConfigured
 from django.test import TestCase
@@ -33,7 +33,7 @@ class BaseModelBackendTest(object):
         ContentType.objects.clear_cache()
 
     def test_has_perm(self):
-        user = self.UserModel.objects.get(username='test')
+        user = self.UserModel.objects.get(pk=self.user.pk)
         self.assertEqual(user.has_perm('auth.test'), False)
         user.is_staff = True
         user.save()
@@ -52,14 +52,14 @@ class BaseModelBackendTest(object):
         self.assertEqual(user.has_perm('auth.test'), False)
 
     def test_custom_perms(self):
-        user = self.UserModel.objects.get(username='test')
+        user = self.UserModel.objects.get(pk=self.user.pk)
         content_type = ContentType.objects.get_for_model(Group)
         perm = Permission.objects.create(name='test', content_type=content_type, codename='test')
         user.user_permissions.add(perm)
         user.save()
 
         # reloading user to purge the _perm_cache
-        user = self.UserModel.objects.get(username='test')
+        user = self.UserModel.objects.get(pk=self.user.pk)
         self.assertEqual(user.get_all_permissions() == set(['auth.test']), True)
         self.assertEqual(user.get_group_permissions(), set([]))
         self.assertEqual(user.has_module_perms('Group'), False)
@@ -70,7 +70,7 @@ class BaseModelBackendTest(object):
         perm = Permission.objects.create(name='test3', content_type=content_type, codename='test3')
         user.user_permissions.add(perm)
         user.save()
-        user = self.UserModel.objects.get(username='test')
+        user = self.UserModel.objects.get(pk=self.user.pk)
         self.assertEqual(user.get_all_permissions(), set(['auth.test2', 'auth.test', 'auth.test3']))
         self.assertEqual(user.has_perm('test'), False)
         self.assertEqual(user.has_perm('auth.test'), True)
@@ -80,7 +80,7 @@ class BaseModelBackendTest(object):
         group.permissions.add(perm)
         group.save()
         user.groups.add(group)
-        user = self.UserModel.objects.get(username='test')
+        user = self.UserModel.objects.get(pk=self.user.pk)
         exp = set(['auth.test2', 'auth.test', 'auth.test3', 'auth.test_group'])
         self.assertEqual(user.get_all_permissions(), exp)
         self.assertEqual(user.get_group_permissions(), set(['auth.test_group']))
@@ -92,7 +92,7 @@ class BaseModelBackendTest(object):
 
     def test_has_no_object_perm(self):
         """Regressiontest for #12462"""
-        user = self.UserModel.objects.get(username='test')
+        user = self.UserModel.objects.get(pk=self.user.pk)
         content_type = ContentType.objects.get_for_model(Group)
         perm = Permission.objects.create(name='test', content_type=content_type, codename='test')
         user.user_permissions.add(perm)
@@ -105,7 +105,7 @@ class BaseModelBackendTest(object):
 
     def test_get_all_superuser_permissions(self):
         "A superuser has all permissions. Refs #14795"
-        user = self.UserModel.objects.get(username='test2')
+        user = self.UserModel.objects.get(pk=self.superuser.pk)
         self.assertEqual(len(user.get_all_permissions()), len(Permission.objects.all()))
 
 
@@ -117,12 +117,12 @@ class ModelBackendTest(BaseModelBackendTest, TestCase):
     UserModel = User
 
     def create_users(self):
-        User.objects.create_user(
+        self.user = User.objects.create_user(
             username='test',
             email='test@example.com',
             password='test',
         )
-        User.objects.create_superuser(
+        self.superuser = User.objects.create_superuser(
             username='test2',
             email='test2@example.com',
             password='test',
@@ -150,13 +150,13 @@ class ExtensionUserModelBackendTest(BaseModelBackendTest, TestCase):
     UserModel = ExtensionUser
 
     def create_users(self):
-        ExtensionUser.objects.create_user(
+        self.user = ExtensionUser.objects.create_user(
             username='test',
             email='test@example.com',
             password='test',
             date_of_birth=date(2006, 4, 25)
         )
-        ExtensionUser.objects.create_superuser(
+        self.superuser = ExtensionUser.objects.create_superuser(
             username='test2',
             email='test2@example.com',
             password='test',
@@ -164,6 +164,31 @@ class ExtensionUserModelBackendTest(BaseModelBackendTest, TestCase):
         )
 
 
+@override_settings(AUTH_USER_MODEL='auth.CustomPermissionsUser')
+class CustomPermissionsUserModelBackendTest(BaseModelBackendTest, TestCase):
+    """
+    Tests for the ModelBackend using the CustomPermissionsUser model.
+
+    As with the ExtensionUser test, this isn't a perfect test, because both
+    the User and CustomPermissionsUser are synchronized to the database,
+    which wouldn't ordinary happen in production.
+    """
+
+    UserModel = CustomPermissionsUser
+
+    def create_users(self):
+        self.user = CustomPermissionsUser.objects.create_user(
+            email='test@example.com',
+            password='test',
+            date_of_birth=date(2006, 4, 25)
+        )
+        self.superuser = CustomPermissionsUser.objects.create_superuser(
+            email='test2@example.com',
+            password='test',
+            date_of_birth=date(1976, 11, 8)
+        )
+
+
 class TestObj(object):
     pass
 
diff --git a/django/contrib/auth/tests/custom_user.py b/django/contrib/auth/tests/custom_user.py
index 710ac8bdd7..7e042e4895 100644
--- a/django/contrib/auth/tests/custom_user.py
+++ b/django/contrib/auth/tests/custom_user.py
@@ -1,5 +1,11 @@
 from django.db import models
-from django.contrib.auth.models import BaseUserManager, AbstractBaseUser, AbstractUser, UserManager
+from django.contrib.auth.models import (
+    BaseUserManager,
+    AbstractBaseUser,
+    AbstractUser,
+    UserManager,
+    PermissionsMixin
+)
 
 
 # The custom User uses email as the unique identifier, and requires
@@ -90,6 +96,40 @@ class ExtensionUser(AbstractUser):
         app_label = 'auth'
 
 
+# The CustomPermissionsUser users email as the identifier, but uses the normal
+# Django permissions model. This allows us to check that the PermissionsMixin
+# includes everything that is needed to interact with the ModelBackend.
+
+class CustomPermissionsUserManager(CustomUserManager):
+    def create_superuser(self, email, password, date_of_birth):
+        u = self.create_user(email, password=password, date_of_birth=date_of_birth)
+        u.is_superuser = True
+        u.save(using=self._db)
+        return u
+
+
+class CustomPermissionsUser(AbstractBaseUser, PermissionsMixin):
+    email = models.EmailField(verbose_name='email address', max_length=255, unique=True)
+    date_of_birth = models.DateField()
+
+    objects = CustomPermissionsUserManager()
+
+    USERNAME_FIELD = 'email'
+    REQUIRED_FIELDS = ['date_of_birth']
+
+    class Meta:
+        app_label = 'auth'
+
+    def get_full_name(self):
+        return self.email
+
+    def get_short_name(self):
+        return self.email
+
+    def __unicode__(self):
+        return self.email
+
+
 class IsActiveTestUser1(AbstractBaseUser):
     """
     This test user class and derivatives test the default is_active behavior
@@ -104,4 +144,3 @@ class IsActiveTestUser1(AbstractBaseUser):
         app_label = 'auth'
 
     # the is_active attr is provided by AbstractBaseUser
-
diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt
index 39174299c6..1ac6a4fc50 100644
--- a/docs/topics/auth.txt
+++ b/docs/topics/auth.txt
@@ -2136,6 +2136,76 @@ override any of the definitions that refer to fields on
 :class:`~django.contrib.auth.models.AbstractUser` that aren't on your
 custom User class.
 
+Custom users and permissions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To make it easy to include Django's permission framework into your own User
+class, Django provides :class:`~django.contrib.auth.model.PermissionsMixin`.
+This is an abstract model you can include in the class heirarchy for your User
+model, giving you all the methods and database fields necessary to support
+Django's permission model.
+
+:class:`~django.contrib.auth.model.PermissionsMixin` provides the following
+methods and attributes:
+
+.. class:: models.PermissionsMixin
+
+    .. attribute:: models.PermissionsMixin.is_superuser
+
+        Boolean. Designates that this user has all permissions without
+        explicitly assigning them.
+
+    .. method:: models.PermissionsMixin.get_group_permissions(obj=None)
+
+        Returns a set of permission strings that the user has, through his/her
+        groups.
+
+        If ``obj`` is passed in, only returns the group permissions for
+        this specific object.
+
+    .. method:: models.PermissionsMixin.get_all_permissions(obj=None)
+
+        Returns a set of permission strings that the user has, both through
+        group and user permissions.
+
+        If ``obj`` is passed in, only returns the permissions for this
+        specific object.
+
+    .. method:: models.PermissionsMixin.has_perm(perm, obj=None)
+
+        Returns ``True`` if the user has the specified permission, where perm is
+        in the format ``"<app label>.<permission codename>"`` (see
+        `permissions`_). If the user is inactive, this method will
+        always return ``False``.
+
+        If ``obj`` is passed in, this method won't check for a permission for
+        the model, but for this specific object.
+
+    .. method:: models.PermissionsMixin.has_perms(perm_list, obj=None)
+
+        Returns ``True`` if the user has each of the specified permissions,
+        where each perm is in the format
+        ``"<app label>.<permission codename>"``. If the user is inactive,
+        this method will always return ``False``.
+
+        If ``obj`` is passed in, this method won't check for permissions for
+        the model, but for the specific object.
+
+    .. method:: models.PermissionsMixin.has_module_perms(package_name)
+
+        Returns ``True`` if the user has any permissions in the given package
+        (the Django app label). If the user is inactive, this method will
+        always return ``False``.
+
+.. admonition:: ModelBackend
+
+    If you don't include the
+    :class:`~django.contrib.auth.model.PermissionsMixin`, you must ensure you
+    don't invoke the permissions methods on ``ModelBackend``. ``ModelBackend``
+    assumes that certain fields are available on your user model. If your User
+    model doesn't provide  those fields, you will receive database errors when
+    you check permissions.
+
 Custom users and Proxy models
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~