Index: django/conf/global_settings.py
===================================================================
--- django/conf/global_settings.py	(revision 2849)
+++ django/conf/global_settings.py	(working copy)
@@ -265,3 +265,11 @@
 # A tuple of IP addresses that have been banned from participating in various
 # Django-powered features.
 BANNED_IPS = ()
+
+##################
+# AUTHENTICATION #
+##################
+
+AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',)
+CREDENTIAL_PLUGINS = ('django.contrib.auth.credentials.username_password_form',)
+
Index: django/contrib/auth/backends.py
===================================================================
--- django/contrib/auth/backends.py	(revision 0)
+++ django/contrib/auth/backends.py	(revision 0)
@@ -0,0 +1,52 @@
+from django.conf import settings
+from django.contrib.auth.models import User
+from django.contrib.auth.utils import check_password
+
+class SettingsBackend:
+    """
+    Authenticate against vars in settings.py Use the login name, and a hash 
+    of the password.
+    
+    ADMIN_LOGIN = 'admin'
+    ADMIN_PASSWORD = 'sha1$4e987$afbcf42e21bd417fb71db8c66b321e9fc33051de'
+    """
+    def authenticate(self, username=None, password=None):
+        login_valid = (settings.ADMIN_LOGIN == username)
+        pwd_valid = check_password(password, settings.ADMIN_PASSWORD)
+        if login_valid and pwd_valid:
+            # TODO: This should be abstracted out someplace else.
+            try:
+                user = User.objects.get(username=username)
+            except User.DoesNotExist:
+                user = User(username=username, password='')
+                user.is_staff = True
+                user.is_superuser = True
+                user.save()
+            return user
+        return None
+
+    def get_user(self, user_id):
+        try:
+            return User.objects.get(pk=user_id)
+        except User.DoesNotExist:
+            return None
+
+class ModelBackend:
+    """
+    Authenticate against django.contrib.auth.models.User
+    """
+    # TODO: Model, login attribute name and password attribute name should be
+    # configurable.
+    def authenticate(self, username=None, password=None):
+        try:
+            user = User.objects.get(username=username)
+            if user.check_password(password):
+                return user
+        except User.DoesNotExist:
+            return None
+
+    def get_user(self, user_id):
+        try:
+            return User.objects.get(pk=user_id)
+        except User.DoesNotExist:
+            return None
Index: django/contrib/auth/middleware.py
===================================================================
--- django/contrib/auth/middleware.py	(revision 2849)
+++ django/contrib/auth/middleware.py	(working copy)
@@ -4,12 +4,8 @@
 
     def __get__(self, request, obj_type=None):
         if self._user is None:
-            from django.contrib.auth.models import User, AnonymousUser, SESSION_KEY
-            try:
-                user_id = request.session[SESSION_KEY]
-                self._user = User.objects.get(pk=user_id)
-            except (KeyError, User.DoesNotExist):
-                self._user = AnonymousUser()
+            from django.contrib.auth import get_current_user
+            self._user = get_current_user(request)
         return self._user
 
 class AuthenticationMiddleware:
Index: django/contrib/auth/views.py
===================================================================
--- django/contrib/auth/views.py	(revision 2849)
+++ django/contrib/auth/views.py	(working copy)
@@ -3,7 +3,6 @@
 from django import forms
 from django.shortcuts import render_to_response
 from django.template import RequestContext
-from django.contrib.auth.models import SESSION_KEY
 from django.contrib.sites.models import Site
 from django.http import HttpResponse, HttpResponseRedirect
 from django.contrib.auth.decorators import login_required
@@ -19,7 +18,8 @@
             # Light security check -- make sure redirect_to isn't garbage.
             if not redirect_to or '://' in redirect_to or ' ' in redirect_to:
                 redirect_to = '/accounts/profile/'
-            request.session[SESSION_KEY] = manipulator.get_user_id()
+            from django.contrib.auth import login
+            login(request, manipulator.get_user())
             request.session.delete_test_cookie()
             return HttpResponseRedirect(redirect_to)
     else:
@@ -33,8 +33,9 @@
 
 def logout(request, next_page=None):
     "Logs out the user and displays 'You are logged out' message."
+    from django.contrib.auth import logout
     try:
-        del request.session[SESSION_KEY]
+        logout(request)
     except KeyError:
         return render_to_response('registration/logged_out.html', {'title': 'Logged out'}, context_instance=RequestContext(request))
     else:
Index: django/contrib/auth/credentials.py
===================================================================
--- django/contrib/auth/credentials.py	(revision 0)
+++ django/contrib/auth/credentials.py	(revision 0)
@@ -0,0 +1,13 @@
+def username_password_form(request):
+    try:
+        username = request.POST['username']
+        password = request.POST['password']
+        return {'username': username, 'password': password}
+    except KeyError:
+        return None
+
+def token(request):
+    try:
+        return request.POST['token']
+    except KeyError:
+        return None
Index: django/contrib/auth/__init__.py
===================================================================
--- django/contrib/auth/__init__.py	(revision 2849)
+++ django/contrib/auth/__init__.py	(working copy)
@@ -1,2 +1,115 @@
+from django.core.exceptions import ImproperlyConfigured
+
+SESSION_KEY = '_auth_user_id'
+BACKEND_SESSION_KEY = '_auth_user_backend'
 LOGIN_URL = '/accounts/login/'
 REDIRECT_FIELD_NAME = 'next'
+
+def load_plugin(path):
+    i = path.rfind('.')
+    module, attr = path[:i], path[i+1:]
+    try:
+        mod = __import__(module, '', '', [attr])
+    except ImportError, e:
+        raise ImproperlyConfigured, 'Error importing credential plugin %s: "%s"' % (module, e)
+    try:
+        func = getattr(mod, attr)
+    except AttributeError:
+        raise ImproperlyConfigured, 'Module "%s" does not define a "%s" credential plugin' % (module, attr)
+    return func
+
+def load_backend(path):
+    i = path.rfind('.')
+    module, attr = path[:i], path[i+1:]
+    try:
+        mod = __import__(module, '', '', [attr])
+    except ImportError, e:
+        raise ImproperlyConfigured, 'Error importing authentication backend %s: "%s"' % (module, e)
+    try:
+        cls = getattr(mod, attr)
+    except AttributeError:
+        raise ImproperlyConfigured, 'Module "%s" does not define a "%s" authentication backend' % (module, attr)
+    return cls()
+
+def get_backends():
+    from django.conf import settings
+    backends = []
+    for backend_path in settings.AUTHENTICATION_BACKENDS:
+        backends.append(load_backend(backend_path))
+    return backends
+
+def get_credential_plugins():
+    from django.conf import settings
+    credential_plugins = []
+    for plugin_path in settings.CREDENTIAL_PLUGINS:
+        credential_plugins.append(load_plugin(plugin_path))
+    return credential_plugins
+        
+def authenticate_credentials(**credentials):
+    """
+    If the given credentials, return a user object.
+    """
+    for backend in get_backends():
+        try:
+            user = backend.authenticate(**credentials)
+        except TypeError:
+            # this backend doesn't accept these credentials as arguments, try the next one.
+            continue
+        if user is None:
+            continue
+        # annotate the user object with the path of the backend
+        user.backend = str(backend.__class__)
+        return user
+
+def authenticate_request(request):
+    """
+    Use CREDENTIAL_PLUGINS to find credentials in the request and try to
+    authenticate them.
+    """
+    for plugin in get_credential_plugins():
+        credentials = plugin(request)
+        if credentials is None:
+            continue
+        user = authenticate_credentials(**credentials)
+        if user is None:
+            continue
+        return user
+
+def login(request, user=None):
+    """
+    Persist a user id and a backend in the request. This way a user doesn't
+    have to reauthenticate on every request.
+    """
+    if user is None:
+        user = request.user
+    # TODO: It would be nice to support different login methods, like signed cookies.
+    request.session[SESSION_KEY] = user.id
+    request.session[BACKEND_SESSION_KEY] = user.backend
+
+def authenticate_request_and_login(request):
+    """
+    Convenience function to authenticate a request and log a user in. Returns
+    the user object, or None if authentication failed.
+    """
+    user = authenticate_request(request)
+    if user is not None:
+        login(request, user)
+    return user
+
+def logout(request):
+    """
+    Remove the authenticated user's id from request.
+    """
+    del request.session[SESSION_KEY]
+    del request.session[BACKEND_SESSION_KEY]
+
+def get_current_user(request):
+    from django.contrib.auth.models import AnonymousUser
+    try:
+        user_id = request.session[SESSION_KEY]
+        backend_path = request.session[BACKEND_SESSION_KEY]
+        backend = load_backend(backend_path)
+        user = backend.get_user(user_id) or AnonymousUser()
+    except KeyError:
+        user = AnonymousUser()
+    return user
Index: django/contrib/auth/utils.py
===================================================================
--- django/contrib/auth/utils.py	(revision 0)
+++ django/contrib/auth/utils.py	(revision 0)
@@ -0,0 +1,26 @@
+def encrypt_password(raw_password):
+    import sha, random
+    algo = 'sha1'
+    salt = sha.new(str(random.random())).hexdigest()[:5]
+    hsh = sha.new(salt+raw_password).hexdigest()
+    return '%s$%s$%s' % (algo, salt, hsh)
+
+def check_password(raw_password, enc_password):
+    """
+    Returns a boolean of whether the raw_password was correct. Handles
+    encryption formats behind the scenes.
+    """
+    # Backwards-compatibility check. Older passwords won't include the
+    # algorithm or salt.
+    if '$' not in enc_password:
+        import md5
+        return enc_password == md5.new(raw_password).hexdigest()
+    algo, salt, hsh = enc_password.split('$')
+    if algo == 'md5':
+        import md5
+        return hsh == md5.new(salt+raw_password).hexdigest()
+    elif algo == 'sha1':
+        import sha
+        return hsh == sha.new(salt+raw_password).hexdigest()
+    raise ValueError, "Got unknown password algorithm type in password."
+
Index: django/contrib/auth/models.py
===================================================================
--- django/contrib/auth/models.py	(revision 2849)
+++ django/contrib/auth/models.py	(working copy)
@@ -4,8 +4,6 @@
 from django.utils.translation import gettext_lazy as _
 import datetime
 
-SESSION_KEY = '_auth_user_id'
-
 class SiteProfileNotAvailable(Exception):
     pass
 
Index: django/contrib/auth/forms.py
===================================================================
--- django/contrib/auth/forms.py	(revision 2849)
+++ django/contrib/auth/forms.py	(working copy)
@@ -1,4 +1,5 @@
 from django.contrib.auth.models import User
+from django.contrib.auth import authenticate_request
 from django.contrib.sites.models import Site
 from django.template import Context, loader
 from django.core import validators
@@ -20,8 +21,7 @@
         self.fields = [
             forms.TextField(field_name="username", length=15, maxlength=30, is_required=True,
                 validator_list=[self.isValidUser, self.hasCookiesEnabled]),
-            forms.PasswordField(field_name="password", length=15, maxlength=30, is_required=True,
-                validator_list=[self.isValidPasswordForUser]),
+            forms.PasswordField(field_name="password", length=15, maxlength=30, is_required=True),
         ]
         self.user_cache = None
 
@@ -30,16 +30,10 @@
             raise validators.ValidationError, _("Your Web browser doesn't appear to have cookies enabled. Cookies are required for logging in.")
 
     def isValidUser(self, field_data, all_data):
-        try:
-            self.user_cache = User.objects.get(username=field_data)
-        except User.DoesNotExist:
+        self.user_cache = authenticate_request(self.request)
+        if self.user_cache is None:
             raise validators.ValidationError, _("Please enter a correct username and password. Note that both fields are case-sensitive.")
 
-    def isValidPasswordForUser(self, field_data, all_data):
-        if self.user_cache is not None and not self.user_cache.check_password(field_data):
-            self.user_cache = None
-            raise validators.ValidationError, _("Please enter a correct username and password. Note that both fields are case-sensitive.")
-
     def get_user_id(self):
         if self.user_cache:
             return self.user_cache.id
Index: django/contrib/comments/views/comments.py
===================================================================
--- django/contrib/comments/views/comments.py	(revision 2849)
+++ django/contrib/comments/views/comments.py	(working copy)
@@ -5,7 +5,6 @@
 from django.core.exceptions import ObjectDoesNotExist
 from django.shortcuts import render_to_response
 from django.template import RequestContext
-from django.contrib.auth.models import SESSION_KEY
 from django.contrib.comments.models import Comment, FreeComment, PHOTOS_REQUIRED, PHOTOS_OPTIONAL, RATINGS_REQUIRED, RATINGS_OPTIONAL, IS_PUBLIC
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.auth.forms import AuthenticationForm
@@ -219,7 +218,8 @@
     # If user gave correct username/password and wasn't already logged in, log them in
     # so they don't have to enter a username/password again.
     if manipulator.get_user() and new_data.has_key('password') and manipulator.get_user().check_password(new_data['password']):
-        request.session[SESSION_KEY] = manipulator.get_user_id()
+        from django.contrib.auth import login
+        login(request, manipulator.get_user())
     if errors or request.POST.has_key('preview'):
         class CommentFormWrapper(forms.FormWrapper):
             def __init__(self, manipulator, new_data, errors, rating_choices):
Index: django/contrib/admin/views/decorators.py
===================================================================
--- django/contrib/admin/views/decorators.py	(revision 2849)
+++ django/contrib/admin/views/decorators.py	(working copy)
@@ -1,6 +1,7 @@
 from django import http, template
 from django.conf import settings
-from django.contrib.auth.models import User, SESSION_KEY
+from django.contrib.auth.models import User
+from django.contrib.auth import authenticate_request, login
 from django.shortcuts import render_to_response
 from django.utils.translation import gettext_lazy
 import base64, datetime, md5
@@ -69,11 +70,10 @@
             return _display_login_form(request, message)
 
         # Check the password.
-        username = request.POST.get('username', '')
-        try:
-            user = User.objects.get(username=username, is_staff=True)
-        except User.DoesNotExist:
+        user = authenticate_request(request)
+        if user is None:
             message = ERROR_MESSAGE
+            username = request.POST.get('username', '')
             if '@' in username:
                 # Mistakenly entered e-mail address instead of username? Look it up.
                 try:
@@ -86,8 +86,9 @@
 
         # The user data is correct; log in the user in and continue.
         else:
-            if user.check_password(request.POST.get('password', '')):
-                request.session[SESSION_KEY] = user.id
+            if user.is_staff:
+                login(request, user)
+                # TODO: set last_login with an event.
                 user.last_login = datetime.datetime.now()
                 user.save()
                 if request.POST.has_key('post_data'):
Index: docs/authentication.txt
===================================================================
--- docs/authentication.txt	(revision 2849)
+++ docs/authentication.txt	(working copy)
@@ -269,15 +269,15 @@
 
 To log a user in, do the following within a view::
 
-    from django.contrib.auth.models import SESSION_KEY
-    request.session[SESSION_KEY] = some_user.id
+    from django.contrib.auth import login
+    login(request)
 
-Because this uses sessions, you'll need to make sure you have
+Because the login functions uses sessions, you'll need to make sure you have
 ``SessionMiddleware`` enabled. See the `session documentation`_ for more
 information.
 
-This assumes ``some_user`` is your ``User`` instance. Depending on your task,
-you'll probably want to make sure to validate the user's username and password.
+Depending on your task, you'll probably want to make sure to validate the 
+user's username and password before you log them in.
 
 Limiting access to logged-in users
 ----------------------------------
@@ -611,3 +611,73 @@
 database. To send messages to anonymous users, use the `session framework`_.
 
 .. _session framework: http://www.djangoproject.com/documentation/sessions/
+
+Other Authentication Sources
+============================
+
+Django supports other authentication sources as well. You can even use 
+multiple sources at the same time.
+
+Using multiple backends
+-----------------------
+
+The list of backends to use is controlled by the ``AUTHENTICATION_BACKENDS`` 
+setting. This should be a tuple of python path names. It defaults to 
+``('django.contrib.auth.backends.ModelBackend',)``. To add additional backends
+just add them to your settings.py file. Ordering matters, so if the same
+username and password is valid in multiple backends, the first one in the
+list will return a user object, and the remaining ones won't even get a chance.
+
+
+Customizing ModelBackend
+------------------------
+
+TODO: write me
+
+
+Writing an authentication backend
+---------------------------------
+
+An authentication backend is a class that implements 2 methods: ``get_user(id)``
+and ``authenticate(**credentials)``. The ``get_user`` method takes an id, which 
+could be a username, and database id, whatever, and returns a user object. The 
+``authenticate`` method takes credentials as keyword arguments. Many times it 
+will just look like this::
+
+    class MyBackend:
+        def authenticate(username=None, password=None):
+            # check the username/password and return a user
+
+but it could also authenticate a token like so::
+
+    class MyBackend:
+        def authenticate(token=None):
+            # check the token and return a user
+
+Regardless, ``authenticate`` should check the credentials it gets, and if they 
+are valid, it should return a user object that matches those credentials.
+
+The Django admin system is tightly coupled to the Django User object described 
+at the beginning of this document. For now, the best way to deal with this is to
+create a Django User object for each user that exists for your backend (i.e.
+in your ldap directory, your external sql database, etc.) You can either 
+write a script to do this in advance, or your ``authenticate`` method can do 
+it the first time a user logs in. `django.contrib.auth.backends.SettingsBackend`_
+is an example of the latter approach. Note that you don't have to save a user's
+password in the Django User object. Your backend can still check the password
+against an external source, and return a Django User object.
+
+.. _django.contrib.auth.backends.SettingsBackend: http://code.djangoproject.com/browser/django/branches/magic-removal/django/contrib/auth/backends.py
+
+Credential Plugins
+==================
+
+Like metaclasses, 99.999% of people should never need to bother with these.
+I'm not sure they're even worth keeping... at least conceptually. The work
+they do is probably best done in a few utility functions that get used in 
+decorators or views. Basically, they allow using different types of
+credentials for the same view or url. The main use I can think of is REST, or
+changing your whole app from form/cookie auth, to http basic or digest auth. I
+don't think that will happen very often, if ever.
+
+TODO: write more... maybe
\ No newline at end of file
