Index: django/contrib/auth/handlers/modpython.py
===================================================================
--- django/contrib/auth/handlers/modpython.py	(revision 8630)
+++ django/contrib/auth/handlers/modpython.py	(working copy)
@@ -1,56 +1,147 @@
 from mod_python import apache
 import os
+from urllib import quote
+from django.core import signals
+from django.core.handlers.base import BaseHandler
+from django.core.handlers.modpython import ModPythonRequest
+from django.conf import settings
+from django.contrib.auth import authenticate, REDIRECT_FIELD_NAME
+from django.utils.encoding import iri_to_uri
 
-def authenhandler(req, **kwargs):
+_str_to_bool = lambda s: s.lower() in ('1', 'true', 'on', 'yes')
+
+class ModPythonAuthOptions:
+    def __init__(self, req):
+        options = req.get_options()
+        self.permission_name = options.get('DjangoPermissionName', None)
+        self.staff_only = _str_to_bool(options.get('DjangoRequireStaffStatus', "on"))
+        self.superuser_only = _str_to_bool(options.get('DjangoRequireSuperuserStatus', "off"))
+        self.raise_forbidden = _str_to_bool(options.get('DjangoRaiseForbidden', "off"))
+        self.settings_module = options.get('DJANGO_SETTINGS_MODULE', None)
+
+def setup_environment(req, options):
     """
-    Authentication handler that checks against Django's auth database.
+    mod_python fakes the environ, and thus doesn't process SetEnv. This ensures
+    any future imports relying on settings will work.
     """
-
-    # mod_python fakes the environ, and thus doesn't process SetEnv.  This fixes
-    # that so that the following import works
     os.environ.update(req.subprocess_env)
+    if options.settings_module:
+        os.environ['DJANGO_SETTINGS_MODULE'] = options.settings_module
 
-    # apache 2.2 requires a call to req.get_basic_auth_pw() before 
-    # req.user and friends are available.
-    req.get_basic_auth_pw()
+def authenticate_user(user):
+    if user is None:
+        return False
+    if hasattr(user, 'is_authenticated') and not user.is_authenticated():
+        return False
+    return True
 
-    # check for PythonOptions
-    _str_to_bool = lambda s: s.lower() in ('1', 'true', 'on', 'yes')
+def validate_user(user, options):
+    if hasattr(user, 'is_active') and not user.is_active:
+        return False
+    if options.staff_only and not getattr(user, 'is_staff', None):
+        return False
+    if options.superuser_only and not getattr(user, 'is_superuser', None):
+        return False
+    # If a permission is required then user must have a has_perm function to
+    # validate.
+    if options.permission_name and (not hasattr(user, 'has_perm') or
+                                    not user.has_perm(options.permission_name)):
+        return False
+    return True
 
-    options = req.get_options()
-    permission_name = options.get('DjangoPermissionName', None)
-    staff_only = _str_to_bool(options.get('DjangoRequireStaffStatus', "on"))
-    superuser_only = _str_to_bool(options.get('DjangoRequireSuperuserStatus', "off"))
-    settings_module = options.get('DJANGO_SETTINGS_MODULE', None)
-    if settings_module:
-        os.environ['DJANGO_SETTINGS_MODULE'] = settings_module
+def redirect_to_login(req):
+    path = quote(req.uri)
+    if req.args:
+        path = '%s?%s' % (path, req.args)
+    path = quote(path)
+    iri = '%s?%s=%s' % (settings.LOGIN_URL, REDIRECT_FIELD_NAME, path)
+    uri = iri_to_uri(iri)
+    req.err_headers_out.add('Location', uri)
+    if req.proto_num >= 1001:
+        # Use HTTP Error 303 (see other) for HTTP/1.1 browsers.
+        raise apache.SERVER_RETURN, apache.HTTP_SEE_OTHER
+    else:
+        # Otherwise use HTTP Error 302 (moved temporarily).
+        raise apache.SERVER_RETURN, apache.HTTP_MOVED_TEMPORARILY
 
-    from django.contrib.auth.models import User
-    from django import db
-    db.reset_queries()
+def authenhandler(req, **kwargs):
+    """
+    mod_python authentication handler that checks against Django's auth
+    database.
+    """
+    options = ModPythonAuthOptions(req)
+    setup_environment(req, options)
 
-    # check that the username is valid
-    kwargs = {'username': req.user, 'is_active': True}
-    if staff_only:
-        kwargs['is_staff'] = True
-    if superuser_only:
-        kwargs['is_superuser'] = True
+    signals.request_started.send(None)
     try:
-        try:
-            user = User.objects.get(**kwargs)
-        except User.DoesNotExist:
+        # This populates req.user too, so it's important to do first.
+        password = req.get_basic_auth_pw()
+
+        # Get the user from any of the installed backends.
+        user = authenticate(username=req.user, password=password)
+
+        if not authenticate_user(user):
+            # Raise unauthorized if the user doesn't authenticate to bring up a
+            # password dialog box to allow the user to authenticate.
             return apache.HTTP_UNAUTHORIZED
-    
-        # check the password and any permission given
-        if user.check_password(req.get_basic_auth_pw()):
-            if permission_name:
-                if user.has_perm(permission_name):
-                    return apache.OK
-                else:
-                    return apache.HTTP_UNAUTHORIZED
-            else:
-                return apache.OK
+
+        # Validate the user
+        if validate_user(user, options):
+            return apache.OK
+
+        # mod_python docs say that HTTP_FORBIDDEN should be raised if the user
+        # authenticates but doesn't validate but Django provides it as an
+        # option, alternately raising HTTP_UNAUTHORIZED again to provide the
+        # option of logging in as an alternate user.
+        if options.raise_forbidden:
+            return apache.HTTP_FORBIDDEN
         else:
-            return apache.HTTP_UNAUTHORIZED
+            return apache.HTTP_UNAUTHORIZED    
     finally:
-        db.connection.close()
+        signals.request_finished.send(None)
+
+def accesshandler(req):
+    """
+    mod_python access handler that uses the contrib.auth framework (with
+    sessions, therefore requiring a session cookie).
+    """
+    options = ModPythonAuthOptions(req)
+    setup_environment(req, options)
+
+    # Set up middleware, now that settings works we can do it now.
+    base_handler = BaseHandler()
+    base_handler.load_middleware()
+
+    signals.request_started.send(None)
+    try:
+        request = ModPythonRequest(req)
+
+        # Apply request middleware
+        for middleware_method in base_handler._request_middleware:
+            response = middleware_method(request)
+            if response:
+                # If we get a response then there's no need to keep processing
+                # any remaining request middleware.
+                break
+
+        user = getattr(request, 'user', None)
+        if not authenticate_user(user):
+            # Rather than raising HTTP_UNAUTHORIZED (which the browser won't be
+            # able to handle since this isn't basic HTTP authentication), write
+            # a response which redirects to settings.LOGIN_URL
+            redirect_to_login(req)
+
+        if validate_user(user, options):
+            return apache.OK
+
+        # mod_python docs say that HTTP_FORBIDDEN should be raised if the user
+        # authenticates but doesn't validate but Django provides it as an
+        # option, alternately redirecting to login to provide the option of
+        # logging in as an alternate user.
+        if options.raise_forbidden:
+            return apache.HTTP_FORBIDDEN
+        else:
+            redirect_to_login(req)
+
+    finally:
+        signals.request_finished.send(None)
Index: docs/howto/apache-auth.txt
===================================================================
--- docs/howto/apache-auth.txt	(revision 8630)
+++ docs/howto/apache-auth.txt	(working copy)
@@ -22,13 +22,27 @@
 Configuring Apache
 ==================
 
-To check against Django's authorization database from a Apache configuration
-file, you'll need to use mod_python's ``PythonAuthenHandler`` directive along
-with the standard ``Auth*`` and ``Require`` directives:
+To check against Django's authorization database from an Apache configuration
+file, you can either use mod_python's ``PythonAccessHandler`` directive or 
+the ``PythonAuthenHandler`` directive along with the standard ``Auth*`` and
+``Require`` directives.
 
+The ``PythonAccessHandler`` directive validates using the built-in
+``contrib.auth`` authentication, which uses the session cookie and redirects to
+the ``settings.LOGIN_URL`` if authentication is required::
+
 .. code-block:: apache
 
     <Location /example/>
+        SetEnv DJANGO_SETTINGS_MODULE mysite.settings
+        PythonAccessHandler django.contrib.auth.handlers.modpython
+    </Location>
+
+The ``PythonAuthenHandler`` directive just uses basic HTTP authentication::
+
+.. code-block:: apache
+
+    <Location /example/>
         AuthType Basic
         AuthName "example.com"
         Require valid-user
@@ -110,8 +124,25 @@
 
                                       By default no specific permission will be
                                       required.
+
+    ``DjangoRaiseForbidden``          If the user authenticates but does not
+                                      have the valid credentials, raise HTTP
+                                      Error 403 (forbidden) rather providing a
+                                      login prompt again.
+
+                                      Defaults to ``off``.
     ================================  =========================================
 
+You may also want to make Apache pass the correct headers to stop proxy servers
+(and local browser caches) from caching your protected resources. Assuming that
+the ``mod_expires`` and ``mod_headers`` modules are enabled on your Apache
+server, you can add the following directives to your ``Location`` block::
+
+    # Stop resources from being cached
+    ExpiresActive On
+    ExpiresDefault A0
+    Header append Cache-Control: "no-cache, no-store, must-revalidate, private"
+
 Note that sometimes ``SetEnv`` doesn't play well in this mod_python
 configuration, for reasons unknown. If you're having problems getting
 mod_python to recognize your ``DJANGO_SETTINGS_MODULE``, you can set it using
@@ -120,3 +151,9 @@
 
     SetEnv DJANGO_SETTINGS_MODULE mysite.settings
     PythonOption DJANGO_SETTINGS_MODULE mysite.settings
+
+.. _authentication system: ../authentication/
+.. _Subversion: http://subversion.tigris.org/
+.. _mod_dav: http://httpd.apache.org/docs/2.0/mod/mod_dav.html
+.. _custom permissions: ../authentication/#custom-permissions
+
