diff -r 6490bd644de2 django/core/context_processors.py
--- a/django/core/context_processors.py	Thu Oct 08 23:40:00 2009 +0100
+++ b/django/core/context_processors.py	Wed Oct 14 14:33:28 2009 +0100
@@ -8,6 +8,8 @@
 """
 
 from django.conf import settings
+from django.template.utils import ContextLazyObject
+from django.utils.functional import lazy, memoize
 
 def auth(request):
     """
@@ -17,15 +19,26 @@
     If there is no 'user' attribute in the request, uses AnonymousUser (from
     django.contrib.auth).
     """
-    if hasattr(request, 'user'):
-        user = request.user
-    else:
-        from django.contrib.auth.models import AnonymousUser
-        user = AnonymousUser()
+    # If we access request.user, request.session is accessed, which results in
+    # 'Vary: Cookie' being sent in every request that uses this context
+    # processor, which can easily be every request on a site if
+    # TEMPLATE_CONTEXT_PROCESSORS has this context processor added.  This kills
+    # the ability to cache.  So, we carefully ensure these attributes are lazy.
+    # We don't use django.utils.functional.lazy() for User, because that
+    # requires knowing the class of the object we want to proxy, which could
+    # break with custom auth backends.  LazyObject is a less complete but more
+    # flexible solution that is a good enough wrapper for 'User'.
+    def get_user():
+        if hasattr(request, 'user'):
+            return request.user
+        else:
+            from django.contrib.auth.models import AnonymousUser
+            return AnonymousUser()
+
     return {
-        'user': user,
-        'messages': user.get_and_delete_messages(),
-        'perms': PermWrapper(user),
+        'user': ContextLazyObject(get_user),
+        'messages': lazy(memoize(lambda: get_user().get_and_delete_messages(), {}, 0), list)(),
+        'perms':  lazy(lambda: PermWrapper(get_user()), PermWrapper)(),
     }
 
 def debug(request):
@@ -79,7 +92,7 @@
 
     def __getitem__(self, module_name):
         return PermLookupDict(self.user, module_name)
-        
+
     def __iter__(self):
         # I am large, I contain multitudes.
         raise TypeError("PermWrapper is not iterable.")
diff -r 6490bd644de2 django/template/utils.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/django/template/utils.py	Wed Oct 14 14:33:28 2009 +0100
@@ -0,0 +1,24 @@
+from django.utils.functional import LazyObject
+
+class ContextLazyObject(LazyObject):
+    """
+    A lazy object initialised from any function, useful for lazily
+    adding things to the Context.
+
+    Works best for compound objects of unknown type where you are only
+    interested in the attributes, and won't need things like str(obj).  For
+    simple objects of known type, use django.utils.functional.lazy
+
+    For sequences, use a generator instead.
+    """
+    def __init__(self, func):
+        """
+        Pass in a callable that returns the actual value to be used
+        """
+        self.__dict__['_setupfunc'] = func
+        # For some reason, we have to inline LazyObject.__init__ here to avoid
+        # recursion
+        self._wrapped = None
+
+    def _setup(self):
+        self._wrapped = self._setupfunc()
diff -r 6490bd644de2 tests/regressiontests/context_processors/fixtures/context-processors-users.xml
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/regressiontests/context_processors/fixtures/context-processors-users.xml	Wed Oct 14 14:33:28 2009 +0100
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<django-objects version="1.0">
+    <object pk="100" model="auth.user">
+        <field type="CharField" name="username">super</field>
+        <field type="CharField" name="first_name">Super</field>
+        <field type="CharField" name="last_name">User</field>
+        <field type="CharField" name="email">super@example.com</field>
+        <field type="CharField" name="password">sha1$995a3$6011485ea3834267d719b4c801409b8b1ddd0158</field>
+        <field type="BooleanField" name="is_staff">True</field>
+        <field type="BooleanField" name="is_active">True</field>
+        <field type="BooleanField" name="is_superuser">True</field>
+        <field type="DateTimeField" name="last_login">2007-05-30 13:20:10</field>
+        <field type="DateTimeField" name="date_joined">2007-05-30 13:20:10</field>
+        <field to="auth.group" name="groups" rel="ManyToManyRel"></field>
+        <field to="auth.permission" name="user_permissions" rel="ManyToManyRel"></field>
+    </object>
+</django-objects>
diff -r 6490bd644de2 tests/regressiontests/context_processors/templates/context_processors/auth_attrs_access.html
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_access.html	Wed Oct 14 14:33:28 2009 +0100
@@ -0,0 +1,1 @@
+{{ user.id }}
diff -r 6490bd644de2 tests/regressiontests/context_processors/templates/context_processors/auth_attrs_messages.html
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_messages.html	Wed Oct 14 14:33:28 2009 +0100
@@ -0,0 +1,1 @@
+{% for m in messages %}{{ m }}{% endfor %}
diff -r 6490bd644de2 tests/regressiontests/context_processors/templates/context_processors/auth_attrs_perms.html
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_perms.html	Wed Oct 14 14:33:28 2009 +0100
@@ -0,0 +1,1 @@
+{% if perms.auth %}Has auth permissions{% endif %}
diff -r 6490bd644de2 tests/regressiontests/context_processors/templates/context_processors/auth_attrs_test_access.html
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/regressiontests/context_processors/templates/context_processors/auth_attrs_test_access.html	Wed Oct 14 14:33:28 2009 +0100
@@ -0,0 +1,1 @@
+{% if session_accessed %}Session accessed{% else %}Session not accessed{% endif %}
diff -r 6490bd644de2 tests/regressiontests/context_processors/tests.py
--- a/tests/regressiontests/context_processors/tests.py	Thu Oct 08 23:40:00 2009 +0100
+++ b/tests/regressiontests/context_processors/tests.py	Wed Oct 14 14:33:28 2009 +0100
@@ -36,3 +36,37 @@
         self.assertContains(response, url)
         response = self.client.post(url, {'path': '/blah/'})
         self.assertContains(response, url)
+
+class AuthContextProcessorTests(TestCase):
+    """
+    Tests for the ``django.core.context_processors.auth`` processor
+    """
+    urls = 'regressiontests.context_processors.urls'
+    fixtures = ['context-processors-users.xml']
+
+    def test_session_not_accessed(self):
+        """
+        Tests that the session is not accessed simply by including
+        the auth context processor
+        """
+        response = self.client.get('/auth_processor_no_attr_access/')
+        self.assertContains(response, "Session not accessed")
+
+    def test_session_is_accessed(self):
+        """
+        Tests that the session is accessed if the auth context processor
+        is used and relevant attributes accessed.
+        """
+        response = self.client.get('/auth_processor_attr_access/')
+        self.assertContains(response, "Session accessed")
+
+    def test_perms_attrs(self):
+        self.client.login(username='super', password='secret')
+        response = self.client.get('/auth_processor_perms/')
+        self.assertContains(response, "Has auth permissions")
+
+    def test_message_attrs(self):
+        self.client.login(username='super', password='secret')
+        response = self.client.get('/auth_processor_messages/')
+        self.assertContains(response, "Message 1")
+
diff -r 6490bd644de2 tests/regressiontests/context_processors/urls.py
--- a/tests/regressiontests/context_processors/urls.py	Thu Oct 08 23:40:00 2009 +0100
+++ b/tests/regressiontests/context_processors/urls.py	Wed Oct 14 14:33:28 2009 +0100
@@ -5,4 +5,8 @@
 
 urlpatterns = patterns('',
     (r'^request_attrs/$', views.request_processor),
+    (r'^auth_processor_no_attr_access/$', views.auth_processor_no_attr_access),
+    (r'^auth_processor_attr_access/$', views.auth_processor_attr_access),
+    (r'^auth_processor_perms/$', views.auth_processor_perms),
+    (r'^auth_processor_messages/$', views.auth_processor_messages),
 )
diff -r 6490bd644de2 tests/regressiontests/context_processors/views.py
--- a/tests/regressiontests/context_processors/views.py	Thu Oct 08 23:40:00 2009 +0100
+++ b/tests/regressiontests/context_processors/views.py	Wed Oct 14 14:33:28 2009 +0100
@@ -6,3 +6,25 @@
 def request_processor(request):
     return render_to_response('context_processors/request_attrs.html',
         RequestContext(request, {}, processors=[context_processors.request]))
+
+def auth_processor_no_attr_access(request):
+    r1 = render_to_response('context_processors/auth_attrs_no_access.html',
+        RequestContext(request, {}, processors=[context_processors.auth]))
+    # *After* rendering, we check whether the session was accessed
+    return render_to_response('context_processors/auth_attrs_test_access.html',
+        {'session_accessed':request.session.accessed})
+
+def auth_processor_attr_access(request):
+    r1 = render_to_response('context_processors/auth_attrs_access.html',
+        RequestContext(request, {}, processors=[context_processors.auth]))
+    return render_to_response('context_processors/auth_attrs_test_access.html',
+        {'session_accessed':request.session.accessed})
+
+def auth_processor_perms(request):
+    return render_to_response('context_processors/auth_attrs_perms.html',
+        RequestContext(request, {}, processors=[context_processors.auth]))
+
+def auth_processor_messages(request):
+    request.user.message_set.create(message="Message 1")
+    return render_to_response('context_processors/auth_attrs_messages.html',
+         RequestContext(request, {}, processors=[context_processors.auth]))
