Index: django/contrib/csrf/middleware.py
===================================================================
--- django/contrib/csrf/middleware.py	(revision 10369)
+++ django/contrib/csrf/middleware.py	(working copy)
@@ -7,6 +7,7 @@
 
 import re
 import itertools
+import random
 try:
     from functools import wraps
 except ImportError:
@@ -24,29 +25,69 @@
 
 _HTML_TYPES = ('text/html', 'application/xhtml+xml')
 
-def _make_token(session_id):
-    return md5_constructor(settings.SECRET_KEY + session_id).hexdigest()
+# Use the system (hardware-based) random number generator if it exists.
+if hasattr(random, 'SystemRandom'):
+    randrange = random.SystemRandom().randrange
+else:
+    randrange = random.randrange
+_MAX_CSRF_KEY = 18446744073709551616L     # 2 << 63
 
+def _get_new_csrf_key():
+    return md5_constructor("%s%s"
+                % (randrange(0, _MAX_CSRF_KEY), settings.SECRET_KEY)).hexdigest()
+
+def _make_token(csrf_cookie):
+    return md5_constructor("csrf-" + settings.SECRET_KEY + csrf_cookie).hexdigest()
+
+def _cookie_name():
+    # Backwards-compatibility: derive the cookie name from SESSION_COOKIE_NAME, so if the
+    # user has specified a unique session cookie, we use a unique CSRF cookie, too.
+    return "csrf_%s" % settings.SESSION_COOKIE_NAME 
+
+def get_csrf_token(request):
+    """Return the CSRF token to be inserted into forms by CsrfResponseMiddleware."""
+    csrf_cookie = request.META.get("CSRF_COOKIE", None)
+    assert csrf_cookie is not None, 'META["CSRF_COOKIE"] is not set.'
+
+    return _make_token(csrf_cookie)
+
 class CsrfViewMiddleware(object):
     """
     Middleware that requires a present and correct csrfmiddlewaretoken
-    for POST requests that have an active session.
+    for POST requests that have a CSRF cookie.
     """
     def process_view(self, request, callback, callback_args, callback_kwargs):
+        if getattr(callback, 'csrf_exempt', False):
+            return None
+
+        # If the user doesn't have a CSRF cookie, generate one and store it in the
+        # request, so it's available to the view.  We'll store it in a cookie when
+        # we reach the response.
+        try:
+            request.META["CSRF_COOKIE"] = request.COOKIES[_cookie_name()]
+            cookie_is_new = False
+        except KeyError:
+            # No cookie, so create one.
+            request.META["CSRF_COOKIE"] = _get_new_csrf_key()
+            cookie_is_new = True
+
         if request.method == 'POST':
-            if getattr(callback, 'csrf_exempt', False):
-                return None
-
             if request.is_ajax():
                 return None
 
-            try:
-                session_id = request.COOKIES[settings.SESSION_COOKIE_NAME]
-            except KeyError:
-                # No session, no check required
-                return None
+            # If the user didn't already have a CSRF key, then accept the session key for the middleware
+            # token, so CSRF protection isn't lost for the period between upgrading to CSRF cookies to
+            # the first time each user comes back to the site to receive one.
+            if cookie_is_new:
+                try:
+                    csrf_cookie = request.COOKIES[settings.SESSION_COOKIE_NAME]
+                except KeyError:
+                    # No CSRF cookie and no session cookie; no check is performed.
+                    return None
+            else:
+                csrf_cookie = request.META["CSRF_COOKIE"]
 
-            csrf_token = _make_token(session_id)
+            csrf_token = _make_token(csrf_cookie)
             # check incoming token
             try:
                 request_csrf_token = request.POST['csrfmiddlewaretoken']
@@ -60,35 +101,20 @@
 
 class CsrfResponseMiddleware(object):
     """
-    Middleware that post-processes a response to add a
-    csrfmiddlewaretoken if the response/request have an active
-    session.
+    Middleware that post-processes a response to add a csrfmiddlewaretoken.
     """
     def process_response(self, request, response):
         if getattr(response, 'csrf_exempt', False):
             return response
 
-        csrf_token = None
-        try:
-            # This covers a corner case in which the outgoing response
-            # both contains a form and sets a session cookie.  This
-            # really should not be needed, since it is best if views
-            # that create a new session (login pages) also do a
-            # redirect, as is done by all such view functions in
-            # Django.
-            cookie = response.cookies[settings.SESSION_COOKIE_NAME]
-            csrf_token = _make_token(cookie.value)
-        except KeyError:
-            # Normal case - look for existing session cookie
-            try:
-                session_id = request.COOKIES[settings.SESSION_COOKIE_NAME]
-                csrf_token = _make_token(session_id)
-            except KeyError:
-                # no incoming or outgoing cookie
-                pass
+        # Set the CSRF cookie even if it's already set, so we renew the expiry timer.
+        response.set_cookie(_cookie_name(),
+                request.META["CSRF_COOKIE"], max_age = 60 * 60 * 24 * 7 * 52,
+                domain=settings.SESSION_COOKIE_DOMAIN,
+                path=settings.SESSION_COOKIE_PATH)
 
-        if csrf_token is not None and \
-                response['Content-Type'].split(';')[0] in _HTML_TYPES:
+        if response['Content-Type'].split(';')[0] in _HTML_TYPES:
+            csrf_token = get_csrf_token(request)
 
             # ensure we don't add the 'id' attribute twice (HTML validity)
             idattributes = itertools.chain(("id='csrfmiddlewaretoken'",),
@@ -109,18 +135,13 @@
     Request Forgeries by adding hidden form fields to POST forms and
     checking requests for the correct value.
 
-    In the list of middlewares, SessionMiddleware is required, and
-    must come after this middleware.  CsrfMiddleWare must come after
-    compression middleware.
+    CsrfMiddleWare must come after compression middleware.
 
-    If a session ID cookie is present, it is hashed with the
+    A persistent ID cookie is created.  This cookie is hashed with the
     SECRET_KEY setting to create an authentication token.  This token
     is added to all outgoing POST forms and is expected on all
-    incoming POST requests that have a session ID cookie.
+    incoming POST requests that have a CSRF ID cookie.
 
-    If you are setting cookies directly, instead of using Django's
-    session framework, this middleware will not work.
-
     CsrfMiddleWare is composed of two middleware, CsrfViewMiddleware
     and CsrfResponseMiddleware which can be used independently.
     """
Index: django/contrib/csrf/tests.py
===================================================================
--- django/contrib/csrf/tests.py	(revision 10369)
+++ django/contrib/csrf/tests.py	(working copy)
@@ -2,7 +2,7 @@
 
 from django.test import TestCase
 from django.http import HttpRequest, HttpResponse, HttpResponseForbidden
-from django.contrib.csrf.middleware import CsrfMiddleware, _make_token, csrf_exempt
+from django.contrib.csrf.middleware import CsrfMiddleware, _make_token, _cookie_name, csrf_exempt
 from django.conf import settings
 
 
@@ -17,120 +17,157 @@
 
 class CsrfMiddlewareTest(TestCase):
 
-    _session_id = "1"
+    _csrf_id = "1"
 
-    def _get_GET_no_session_request(self):
+    def _get_GET_no_csrf_cookie_request(self):
         return HttpRequest()
 
-    def _get_GET_session_request(self):
-        req = self._get_GET_no_session_request()
-        req.COOKIES[settings.SESSION_COOKIE_NAME] = self._session_id
+    def _get_GET_csrf_cookie_request(self):
+        req = self._get_GET_no_csrf_cookie_request()
+        req.COOKIES[_cookie_name()] = self._csrf_id
         return req
 
-    def _get_POST_session_request(self):
-        req = self._get_GET_session_request()
+    def _get_POST_no_cookie_request(self):
+        req = self._get_GET_no_csrf_cookie_request()
         req.method = "POST"
         return req
 
-    def _get_POST_no_session_request(self):
-        req = self._get_GET_no_session_request()
+    def _get_POST_csrf_cookie_request(self):
+        req = self._get_GET_csrf_cookie_request()
         req.method = "POST"
         return req
 
+    def _get_POST_request_with_token(self):
+        req = self._get_POST_csrf_cookie_request()
+        req.POST['csrfmiddlewaretoken'] = _make_token(self._csrf_id)
+        return req
+
     def _get_POST_session_request_with_token(self):
-        req = self._get_POST_session_request()
-        req.POST['csrfmiddlewaretoken'] = _make_token(self._session_id)
+        req = self._get_POST_no_cookie_request()
+        req.COOKIES[settings.SESSION_COOKIE_NAME] = self._csrf_id
+        req.POST['csrfmiddlewaretoken'] = _make_token(self._csrf_id)
         return req
 
+    def _get_POST_session_request_no_token(self):
+        req = self._get_POST_no_cookie_request()
+        req.COOKIES[settings.SESSION_COOKIE_NAME] = self._csrf_id
+        return req
+
     def _get_post_form_response(self):
         return post_form_response()
 
-    def _get_new_session_response(self):
-        resp = self._get_post_form_response()
-        resp.cookies[settings.SESSION_COOKIE_NAME] = self._session_id
+    def _get_post_form_response_non_html(self):
+        resp = post_form_response()
+        resp["Content-Type"] = "application/xml"
         return resp
 
-    def _check_token_present(self, response):
-        self.assertContains(response, "name='csrfmiddlewaretoken' value='%s'" % _make_token(self._session_id))
+    def _check_token_present(self, response, csrf_id=None):
+        self.assertContains(response, "name='csrfmiddlewaretoken' value='%s'" % _make_token(csrf_id or self._csrf_id))
 
     def get_view(self):
         return test_view
 
     # Check the post processing
-    def test_process_response_no_session(self):
+    def test_process_response_existing_cookie(self):
         """
-        Check the the post-processor does nothing if no session active
+        Check that the token is inserted when a prior CSRF cookie exists.
         """
-        req = self._get_GET_no_session_request()
+        req = self._get_GET_csrf_cookie_request()
+        CsrfMiddleware().process_view(req, self.get_view(), (), {})
+
         resp = self._get_post_form_response()
         resp_content = resp.content # needed because process_response modifies resp
         resp2 = CsrfMiddleware().process_response(req, resp)
-        self.assertEquals(resp_content, resp2.content)
+        self.assertNotEqual(resp_content, resp2.content)
+        self._check_token_present(resp2)
 
-    def test_process_response_existing_session(self):
+    def test_process_response_existing_no_cookie(self):
         """
-        Check that the token is inserted if there is an existing session
+        When no prior CSRF cookie exists, check that the cookie is created and a
+        token is inserted.
         """
-        req = self._get_GET_session_request()
+        req = self._get_GET_no_csrf_cookie_request()
+        CsrfMiddleware().process_view(req, self.get_view(), (), {})
+
         resp = self._get_post_form_response()
         resp_content = resp.content # needed because process_response modifies resp
         resp2 = CsrfMiddleware().process_response(req, resp)
+
+        csrf_id = resp2.cookies.get(_cookie_name(), False)
+        self.assertNotEqual(csrf_id, False)
         self.assertNotEqual(resp_content, resp2.content)
-        self._check_token_present(resp2)
+        self._check_token_present(resp2, csrf_id.value)
 
-    def test_process_response_new_session(self):
+    def test_process_response_no_cookie(self):
         """
-        Check that the token is inserted if there is a new session being started
+        Check the the post-processor does nothing for content-types not in _HTML_TYPES.
         """
-        req = self._get_GET_no_session_request() # no session in request
-        resp = self._get_new_session_response() # but new session started
+        req = self._get_GET_no_csrf_cookie_request()
+        CsrfMiddleware().process_view(req, self.get_view(), (), {})
+        resp = self._get_post_form_response_non_html()
         resp_content = resp.content # needed because process_response modifies resp
         resp2 = CsrfMiddleware().process_response(req, resp)
-        self.assertNotEqual(resp_content, resp2.content)
-        self._check_token_present(resp2)
+        self.assertEquals(resp_content, resp2.content)
 
     def test_process_response_exempt_view(self):
         """
         Check that no post processing is done for an exempt view
         """
-        req = self._get_POST_session_request()
+        req = self._get_POST_csrf_cookie_request()
         resp = csrf_exempt(self.get_view())(req)
         resp_content = resp.content
         resp2 = CsrfMiddleware().process_response(req, resp)
         self.assertEquals(resp_content, resp2.content)
 
     # Check the request processing
-    def test_process_request_no_session(self):
+    def test_process_request_session_cookie_no_csrf_cookie_token(self):
         """
-        Check that if no session is present, the middleware does nothing.
-        to the incoming request.
+        When no CSRF cookie exists, but the user has a session, check that a token
+        using the session cookie as the CSRF cookie is accepted.
         """
-        req = self._get_POST_no_session_request()
+        req = self._get_POST_session_request_with_token()
         req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {})
         self.assertEquals(None, req2)
+    def test_process_request_session_cookie_no_csrf_cookie_no_token(self):
+        """
+        When no CSRF cookie exists, but the user has a session, check that a token
+        using the session cookie as the CSRF cookie is accepted.
+        """
+        req = self._get_POST_session_request_no_token()
+        req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {})
+        self.assertEquals(HttpResponseForbidden, req2.__class__)
 
-    def test_process_request_session_no_token(self):
+    def test_process_request_no_cookie_no_token(self):
         """
-        Check that if a session is present but no token, we get a 'forbidden'
+        Check that if no CSRF cookie is present, the middleware does nothing to
+        the incoming request.
         """
-        req = self._get_POST_session_request()
+        req = self._get_POST_no_cookie_request()
         req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {})
+        self.assertEquals(None, req2)
+
+    def test_process_request_cookie_no_token(self):
+        """
+        Check that if a cookie is present but no token, we get a 'forbidden'
+        """
+        req = self._get_POST_csrf_cookie_request()
+        req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {})
         self.assertEquals(HttpResponseForbidden, req2.__class__)
 
-    def test_process_request_session_and_token(self):
+    def test_process_request_cookie_and_token(self):
         """
-        Check that if a session is present and a token, the middleware lets it through
+        Check that if both a cookie and a token is present, the middleware lets it through.
         """
-        req = self._get_POST_session_request_with_token()
+        req = self._get_POST_request_with_token()
         req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {})
         self.assertEquals(None, req2)
 
-    def test_process_request_session_no_token_exempt_view(self):
+    def test_process_request_cookie_no_token_exempt_view(self):
         """
-        Check that if a session is present and no token, but the csrf_exempt
+        Check that if a cookie is present and no token, but the csrf_exempt
         decorator has been applied to the view, the middleware lets it through
         """
-        req = self._get_POST_session_request()
+        req = self._get_POST_csrf_cookie_request()
         req2 = CsrfMiddleware().process_view(req, csrf_exempt(self.get_view()), (), {})
         self.assertEquals(None, req2)
 
@@ -138,7 +175,7 @@
         """
         Check that AJAX requests are automatically exempted.
         """
-        req = self._get_POST_session_request()
+        req = self._get_POST_csrf_cookie_request()
         req.META['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
         req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {})
         self.assertEquals(None, req2)
Index: docs/ref/contrib/csrf.txt
===================================================================
--- docs/ref/contrib/csrf.txt	(revision 10369)
+++ docs/ref/contrib/csrf.txt	(working copy)
@@ -23,11 +23,9 @@
 =============
 
 Add the middleware ``'django.contrib.csrf.middleware.CsrfMiddleware'`` to
-your list of middleware classes, :setting:`MIDDLEWARE_CLASSES`. It needs to process
-the response after the SessionMiddleware, so must come before it in the
-list. It also must process the response before things like compression
-happen to the response, so it must come after GZipMiddleware in the
-list.
+your list of middleware classes, :setting:`MIDDLEWARE_CLASSES`. It must
+process the response before things like compression happen to the response,
+so it must come after GZipMiddleware in the list.
 
 The ``CsrfMiddleware`` class is actually composed of two middleware:
 ``CsrfViewMiddleware`` which performs the checks on incoming requests,
@@ -70,17 +68,17 @@
 
 CsrfMiddleware does two things:
 
-1. It modifies outgoing requests by adding a hidden form field to all
+1. It stores a random number in a cookie.  For backwards-compatibility
+   purposes, the cookie name is derived from the SESSION_COOKIE_NAME setting.
+
+2. It modifies outgoing requests by adding a hidden form field to all
    'POST' forms, with the name 'csrfmiddlewaretoken' and a value which is
-   a hash of the session ID plus a secret. If there is no session ID set,
-   this modification of the response isn't done, so there is very little
-   performance penalty for those requests that don't have a session.
-   (This is done by ``CsrfResponseMiddleware``).
+   a hash of the cookie value plus a secret.  (This is done by
+   ``CsrfResponseMiddleware``).
 
-2. On all incoming POST requests that have the session cookie set, it
-   checks that the 'csrfmiddlewaretoken' is present and correct. If it
-   isn't, the user will get a 403 error. (This is done by
-   ``CsrfViewMiddleware``)
+3. On all incoming POST requests that have the cookie set, it checks that
+   the 'csrfmiddlewaretoken' is present and correct. If it isn't, the user
+   will get a 403 error. (This is done by ``CsrfViewMiddleware``)
 
 This ensures that only forms that have originated from your Web site
 can be used to POST data back.
@@ -90,10 +88,6 @@
 effects (see `9.1.1 Safe Methods, HTTP 1.1, RFC 2616`_), and so a
 CSRF attack with a GET request ought to be harmless.
 
-POST requests that are not accompanied by a session cookie are not protected,
-but they do not need to be protected, since the 'attacking' Web site
-could make these kind of requests anyway.
-
 The Content-Type is checked before modifying the response, and only
 pages that are served as 'text/html' or 'application/xml+xhtml'
 are modified.
@@ -112,10 +106,6 @@
 Limitations
 ===========
 
-CsrfMiddleware requires Django's session framework to work. If you have
-a custom authentication system that manually sets cookies and the like,
-it won't help you.
-
 If your app creates HTML pages and forms in some unusual way, (e.g.
 it sends fragments of HTML in JavaScript document.write statements)
 you might bypass the filter that adds the hidden field to the form,
