diff --git a/django/contrib/auth/handlers/modwsgi.py b/django/contrib/auth/handlers/modwsgi.py
new file mode 100644
index 0000000000..0e543ef368
--- /dev/null
+++ b/django/contrib/auth/handlers/modwsgi.py
@@ -0,0 +1,43 @@
+from django.contrib.auth.models import User
+from django import db
+from django.utils.encoding import force_bytes
+
+
+def check_password(environ, username, password):
+ """
+ Authenticates against Django's auth database
+
+ mod_wsgi docs specify None, True, False as return value depending
+ on whether the user exists and authenticates.
+ """
+
+ # db connection state is managed similarly to the wsgi handler
+ # as mod_wsgi may call these functions outside of a request/response cycle
+ db.reset_queries()
+
+ try:
+ try:
+ user = User.objects.get(username=username, is_active=True)
+ except User.DoesNotExist:
+ return None
+ return user.check_password(password)
+ finally:
+ db.close_connection()
+
+
+def groups_for_user(environ, username):
+ """
+ Authorizes a user based on groups
+ """
+
+ db.reset_queries()
+
+ try:
+ try:
+ user = User.objects.get(username=username, is_active=True)
+ except User.DoesNotExist:
+ return []
+
+ return [force_bytes(group.name) for group in user.groups.all()]
+ finally:
+ db.close_connection()
diff --git a/django/contrib/auth/tests/__init__.py b/django/contrib/auth/tests/__init__.py
index 094a595238..b3007ea484 100644
--- a/django/contrib/auth/tests/__init__.py
+++ b/django/contrib/auth/tests/__init__.py
@@ -7,6 +7,7 @@ from django.contrib.auth.tests.forms import *
from django.contrib.auth.tests.remote_user import *
from django.contrib.auth.tests.management import *
from django.contrib.auth.tests.models import *
+from django.contrib.auth.tests.handlers import *
from django.contrib.auth.tests.hashers import *
from django.contrib.auth.tests.signals import *
from django.contrib.auth.tests.tokens import *
diff --git a/django/contrib/auth/tests/handlers.py b/django/contrib/auth/tests/handlers.py
new file mode 100644
index 0000000000..f061042ce3
--- /dev/null
+++ b/django/contrib/auth/tests/handlers.py
@@ -0,0 +1,45 @@
+from __future__ import unicode_literals
+
+from django.contrib.auth.handlers.modwsgi import check_password, groups_for_user
+from django.contrib.auth.models import User, Group
+from django.test import TestCase
+
+
+class ModWsgiHandlerTestCase(TestCase):
+ """
+ Tests for the mod_wsgi authentication handler
+ """
+
+ def setUp(self):
+ user1 = User.objects.create_user('test', 'test@example.com', 'test')
+ User.objects.create_user('test1', 'test1@example.com', 'test1')
+
+ group = Group.objects.create(name='test_group')
+ user1.groups.add(group)
+
+ def test_check_password(self):
+ """
+ Verify that check_password returns the correct values as per
+ http://code.google.com/p/modwsgi/wiki/AccessControlMechanisms#Apache_Authentication_Provider
+ """
+
+ # User not in database
+ self.assertTrue(check_password({}, 'unknown', '') is None)
+
+ # Valid user with correct password
+ self.assertTrue(check_password({}, 'test', 'test'))
+
+ # Valid user with incorrect password
+ self.assertFalse(check_password({}, 'test', 'incorrect'))
+
+ def test_groups_for_user(self):
+ """
+ Check that groups_for_user returns correct values as per
+ http://code.google.com/p/modwsgi/wiki/AccessControlMechanisms#Apache_Group_Authorisation
+ """
+
+ # User not in database
+ self.assertEqual(groups_for_user({}, 'unknown'), [])
+
+ self.assertEqual(groups_for_user({}, 'test'), [b'test_group'])
+ self.assertEqual(groups_for_user({}, 'test1'), [])
diff --git a/docs/howto/apache-auth.txt b/docs/howto/apache-auth.txt
deleted file mode 100644
index 719fbc1769..0000000000
--- a/docs/howto/apache-auth.txt
+++ /dev/null
@@ -1,45 +0,0 @@
-=========================================================
-Authenticating against Django's user database from Apache
-=========================================================
-
-Since keeping multiple authentication databases in sync is a common problem when
-dealing with Apache, you can configuring Apache to authenticate against Django's
-:doc:`authentication system ` directly. This requires Apache
-version >= 2.2 and mod_wsgi >= 2.0. For example, you could:
-
-* Serve static/media files directly from Apache only to authenticated users.
-
-* Authenticate access to a Subversion_ repository against Django users with
- a certain permission.
-
-* Allow certain users to connect to a WebDAV share created with mod_dav_.
-
-.. _Subversion: http://subversion.tigris.org/
-.. _mod_dav: http://httpd.apache.org/docs/2.2/mod/mod_dav.html
-
-Configuring Apache
-==================
-
-To check against Django's authorization database from a Apache configuration
-file, you'll need to set 'wsgi' as the value of ``AuthBasicProvider`` or
-``AuthDigestProvider`` directive and then use the ``WSGIAuthUserScript``
-directive to set the path to your authentification script:
-
-.. code-block:: apache
-
-
- AuthType Basic
- AuthName "example.com"
- AuthBasicProvider wsgi
- WSGIAuthUserScript /usr/local/wsgi/scripts/auth.wsgi
- Require valid-user
-
-
-Your auth.wsgi script will have to implement either a
-``check_password(environ, user, password)`` function (for ``AuthBasicProvider``)
-or a ``get_realm_hash(environ, user, realm)`` function (for ``AuthDigestProvider``).
-
-See the `mod_wsgi documentation`_ for more details about the implementation
-of such a solution.
-
-.. _mod_wsgi documentation: http://code.google.com/p/modwsgi/wiki/AccessControlMechanisms#Apache_Authentication_Provider
diff --git a/docs/howto/deployment/wsgi/apache-auth.txt b/docs/howto/deployment/wsgi/apache-auth.txt
new file mode 100644
index 0000000000..36e3d0233c
--- /dev/null
+++ b/docs/howto/deployment/wsgi/apache-auth.txt
@@ -0,0 +1,122 @@
+=========================================================
+Authenticating against Django's user database from Apache
+=========================================================
+
+Since keeping multiple authentication databases in sync is a common problem when
+dealing with Apache, you can configure Apache to authenticate against Django's
+:doc:`authentication system ` directly. This requires Apache
+version >= 2.2 and mod_wsgi >= 2.0. For example, you could:
+
+* Serve static/media files directly from Apache only to authenticated users.
+
+* Authenticate access to a Subversion_ repository against Django users with
+ a certain permission.
+
+* Allow certain users to connect to a WebDAV share created with mod_dav_.
+
+.. _Subversion: http://subversion.tigris.org/
+.. _mod_dav: http://httpd.apache.org/docs/2.2/mod/mod_dav.html
+
+Authentication with mod_wsgi
+============================
+
+Make sure that mod_wsgi is installed and activated and that you have
+followed the steps to setup
+:doc:`Apache with mod_wsgi `
+
+Next, edit your Apache configuration to add a location that you want
+only authenticated users to be able to view:
+
+.. code-block:: apache
+
+ WSGIScriptAlias / /path/to/mysite/config/mysite.wsgi
+
+ WSGIProcessGroup %{GLOBAL}
+ WSGIApplicationGroup django
+
+
+ AuthType Basic
+ AuthName "Top Secret"
+ Require valid-user
+ AuthBasicProvider wsgi
+ WSGIAuthUserScript /path/to/mysite/config/mysite.wsgi
+
+
+The ``WSGIAuthUserScript`` directive tells mod_wsgi to execute the
+``check_password`` function in specified wsgi script, passing the user name and
+password that it receives from the prompt. In this example, the
+``WSGIAuthUserScript`` is the same as the ``WSGIScriptAlias`` that defines your
+application :doc:`that is created by django-admin.py startproject
+`.
+
+.. admonition:: Using Apache 2.2 with authentication
+
+ Make sure that ``mod_auth_basic`` and ``mod_authz_user`` are loaded.
+
+ These might be compiled statically into Apache, or you might need to use
+ LoadModule to load them dynamically in your ``httpd.conf``:
+
+ .. code-block:: apache
+
+ LoadModule auth_basic_module modules/mod_auth_basic.so
+ LoadModule authz_user_module modules/mod_authz_user.so
+
+Finally, edit your WSGI script ``mysite.wsgi`` to tie Apache's
+authentication to your site's authentication mechanisms by importing the
+check_user function:
+
+.. code-block:: python
+
+ import os
+ import sys
+
+ os.environ['DJANGO_SETTINGS_MODULE'] = 'mysite.settings'
+
+ from django.contrib.auth.handlers.modwsgi import check_user
+
+ from django.core.handlers.wsgi import WSGIHandler
+ application = WSGIHandler()
+
+
+Requests beginning with ``/secret/`` will now require a user to authenticate.
+
+The mod_wsgi `access control mechanisms documentation`_ provides additional
+details and information about alternative methods of authentication.
+
+.. _access control mechanisms documentation: http://code.google.com/p/modwsgi/wiki/AccessControlMechanisms
+
+Authorization with mod_wsgi and Django groups
+---------------------------------------------
+
+mod_wsgi also provides functionality to restrict a particular location to
+members of a group.
+
+In this case, the Apache configuration should look like this:
+
+.. code-block:: apache
+
+ WSGIScriptAlias / /path/to/mysite/config/mysite.wsgi
+
+ WSGIProcessGroup %{GLOBAL}
+ WSGIApplicationGroup django
+
+
+ AuthType Basic
+ AuthName "Top Secret"
+ AuthBasicProvider wsgi
+ WSGIAuthUserScript /path/to/mysite/config/mysite.wsgi
+ WSGIAuthGroupScript /path/to/mysite/config/mysite.wsgi
+ Require group secret-agents
+ Require valid-user
+
+
+To support the ``WSGIAuthGroupScript`` directive, the same WSGI script
+``mysite.wsgi`` must also import the ``groups_for_user`` function which
+returns a list groups the given user belongs to.
+
+.. code-block:: python
+
+ from django.contrib.auth.handlers.modwsgi import check_user, groups_for_user
+
+Requests for ``/secret/`` will now also require user to be a member of the
+"secret-agents" group.
diff --git a/docs/howto/deployment/wsgi/index.txt b/docs/howto/deployment/wsgi/index.txt
index ecb302cee3..769d406b1b 100644
--- a/docs/howto/deployment/wsgi/index.txt
+++ b/docs/howto/deployment/wsgi/index.txt
@@ -16,6 +16,7 @@ documentation for the following WSGI servers:
:maxdepth: 1
modwsgi
+ apache-auth
gunicorn
uwsgi
diff --git a/docs/howto/deployment/wsgi/modwsgi.txt b/docs/howto/deployment/wsgi/modwsgi.txt
index 8398f12eb7..b525255dbd 100644
--- a/docs/howto/deployment/wsgi/modwsgi.txt
+++ b/docs/howto/deployment/wsgi/modwsgi.txt
@@ -177,6 +177,13 @@ other approaches:
3. Copy the admin static files so that they live within your Apache
document root.
+Authenticating against Django's user database from Apache
+=========================================================
+
+Django provides a handler to allow Apache to authenticate users directly
+against Django's authentication backends. See the :doc:`mod_wsgi authentication
+documentation `.
+
If you get a UnicodeEncodeError
===============================
diff --git a/docs/howto/index.txt b/docs/howto/index.txt
index 737ee71da4..d39222be26 100644
--- a/docs/howto/index.txt
+++ b/docs/howto/index.txt
@@ -9,7 +9,6 @@ you quickly accomplish common tasks.
.. toctree::
:maxdepth: 1
- apache-auth
auth-remote-user
custom-management-commands
custom-model-fields
diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt
index df8d89c185..fddd03d421 100644
--- a/docs/releases/1.5.txt
+++ b/docs/releases/1.5.txt
@@ -146,6 +146,9 @@ Django 1.5 also includes several smaller improvements worth noting:
configuration duplication. More information can be found in the
:func:`~django.contrib.auth.decorators.login_required` documentation.
+* Django now provides a mod_wsgi :doc:`auth handler
+ `
+
Backwards incompatible changes in 1.5
=====================================