From c88ad327d4f7a0b59cd20f2c222d40026e654826 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Thu, 11 Dec 2014 15:31:01 +0100 Subject: [PATCH 1/3] explain how to work with cookies and sessions when caching --- book/http_cache.rst | 2 + cookbook/cache/varnish.rst | 82 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/book/http_cache.rst b/book/http_cache.rst index 658288f1958..cfe569e80d1 100644 --- a/book/http_cache.rst +++ b/book/http_cache.rst @@ -383,6 +383,8 @@ This has two very reasonable consequences: blog post). Caching them would prevent certain requests from hitting and mutating your application. +.. _http-cache-defaults: + Caching Rules and Defaults ~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/cookbook/cache/varnish.rst b/cookbook/cache/varnish.rst index 6c53138fdd9..6ab7d11a264 100644 --- a/cookbook/cache/varnish.rst +++ b/cookbook/cache/varnish.rst @@ -60,6 +60,87 @@ If the ``X-Forwarded-Port`` header is not set correctly, Symfony will append the port where the PHP application is running when generating absolute URLs, e.g. ``http://example.com:8080/my/path``. +Session Cookies and Caching +--------------------------- + +By default, a sane caching proxy does not cache anything when a request is sent +with :ref:`cookies or a basic authentication header`. +This is because the content of the page is supposed to depend on the cookie +value or authentication header. + +If you know for sure that the backend never uses sessions or basic +authentication, have varnish remove the corresponding header from requests to +prevent clients from bypassing the cache. In practice, you will need sessions +at least for some parts of the site, e.g. when using forms with +:ref:`CSRF Protection `. In this situation, make sure to only +start a session when actually needed, and clear the session when it is no +longer needed. + +.. todo link to cookbook/session/avoid_session_start once https://github.com/symfony/symfony-docs/pull/4661 is merged + +Cookies can also be created in Javascript and used only in the frontend, e.g. +Google analytics. These cookies do not matter for the backend and should not +affect the caching decision. Configure your Varnish cache to +`clean the cookies header`_. Unless you changed the PHP configuration, your session +cookie has the name PHPSESSID: + +.. code-block:: varnish4 + + sub vcl_recv { + if (req.http.Cookie) { + set req.http.Cookie = ";" + req.http.Cookie; + set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";"); + set req.http.Cookie = regsuball(req.http.Cookie, ";(PHPSESSID)=", "; \1="); + set req.http.Cookie = regsuball(req.http.Cookie, ";[^ ][^;]*", ""); + set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", ""); + + if (req.http.Cookie == "") { + remove req.http.Cookie; + } + } + } + +If only small parts of your application depend on cookies (e.g. you display the +username in the header), you can use :ref:`ESI ` for those +fragments. Configure Varnish to store and look up requests in its cache even if +Cookies are present in the request: + +.. code-block:: varnish4 + + sub vcl_recv() { + if (req.http.Cookie) { + /* Force cache lookup for requests with cookies */ + return (lookup); + } + } + +You need to make sure that your backend correctly sets the ``Vary`` header to +tell which responses vary on the cookie and which are the same:: + + public function loginAction() + { + // ... + $response->setVary('Cookie'); + // ... + } + +Only set the ``Vary: Cookie`` header on actions that actually depend on whether +the user is logged in, but not on any other actions. + +.. caution:: + + Be sure to test your setup. If you do not ``Vary`` content that depends on + the session, users will see content from somebody else. If you ``Vary`` too + much, the Varnish cache will be filled with duplicate content for every + user, rendering the cache pointless as cache hits will become rare. + +.. tip:: + + If content is not different for every user, but depends on the roles of a + user, a solution is to separate the cache per group. This pattern is + implemented and explained by the FOSHttpCacheBundle_ under the name + *User Context*. + Ensure Consistent Caching Behaviour ----------------------------------- @@ -169,6 +250,7 @@ proxy before it has expired, it adds complexity to your caching setup. .. _`Varnish`: https://www.varnish-cache.org .. _`Edge Architecture`: http://www.w3.org/TR/edge-arch .. _`GZIP and Varnish`: https://www.varnish-cache.org/docs/3.0/phk/gzip.html +.. _`Clean the cookies header`: https://www.varnish-cache.org/trac/wiki/VCLExampleRemovingSomeCookies .. _`Surrogate-Capability Header`: http://www.w3.org/TR/edge-arch .. _`cache invalidation`: http://tools.ietf.org/html/rfc2616#section-13.10 .. _`FOSHttpCacheBundle`: http://foshttpcachebundle.readthedocs.org/ From 7a4dafc505e36286760fae95403ac18dd73d9363 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Wed, 31 Dec 2014 13:12:31 +0100 Subject: [PATCH 2/3] remove part about vary on cookie --- cookbook/cache/varnish.rst | 51 +++++++------------------------------- 1 file changed, 9 insertions(+), 42 deletions(-) diff --git a/cookbook/cache/varnish.rst b/cookbook/cache/varnish.rst index 6ab7d11a264..a4e1adb5a1d 100644 --- a/cookbook/cache/varnish.rst +++ b/cookbook/cache/varnish.rst @@ -60,8 +60,8 @@ If the ``X-Forwarded-Port`` header is not set correctly, Symfony will append the port where the PHP application is running when generating absolute URLs, e.g. ``http://example.com:8080/my/path``. -Session Cookies and Caching ---------------------------- +Cookies and Caching +------------------- By default, a sane caching proxy does not cache anything when a request is sent with :ref:`cookies or a basic authentication header`. @@ -76,13 +76,14 @@ at least for some parts of the site, e.g. when using forms with start a session when actually needed, and clear the session when it is no longer needed. -.. todo link to cookbook/session/avoid_session_start once https://github.com/symfony/symfony-docs/pull/4661 is merged +.. todo link "CSRF Protection" to https://github.com/symfony/symfony-docs/pull/4141 +.. todo link "only start a session when actually needed" to cookbook/session/avoid_session_start once https://github.com/symfony/symfony-docs/pull/4661 is merged -Cookies can also be created in Javascript and used only in the frontend, e.g. -Google analytics. These cookies do not matter for the backend and should not -affect the caching decision. Configure your Varnish cache to -`clean the cookies header`_. Unless you changed the PHP configuration, your session -cookie has the name PHPSESSID: +Cookies created in Javascript and used only in the frontend, e.g. when using +Google analytics are nonetheless sent to the server. These cookies are not +relevant for the backend and should not affect the caching decision. Configure +your Varnish cache to `clean the cookies header`_. Unless you changed the +default configuration of PHP, your session cookie has the name PHPSESSID: .. code-block:: varnish4 @@ -100,40 +101,6 @@ cookie has the name PHPSESSID: } } -If only small parts of your application depend on cookies (e.g. you display the -username in the header), you can use :ref:`ESI ` for those -fragments. Configure Varnish to store and look up requests in its cache even if -Cookies are present in the request: - -.. code-block:: varnish4 - - sub vcl_recv() { - if (req.http.Cookie) { - /* Force cache lookup for requests with cookies */ - return (lookup); - } - } - -You need to make sure that your backend correctly sets the ``Vary`` header to -tell which responses vary on the cookie and which are the same:: - - public function loginAction() - { - // ... - $response->setVary('Cookie'); - // ... - } - -Only set the ``Vary: Cookie`` header on actions that actually depend on whether -the user is logged in, but not on any other actions. - -.. caution:: - - Be sure to test your setup. If you do not ``Vary`` content that depends on - the session, users will see content from somebody else. If you ``Vary`` too - much, the Varnish cache will be filled with duplicate content for every - user, rendering the cache pointless as cache hits will become rare. - .. tip:: If content is not different for every user, but depends on the roles of a From b294b245a1dc57fbd80cd2f5e146c0a98e8dd746 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Thu, 1 Jan 2015 15:07:44 +0100 Subject: [PATCH 3/3] cleanup from feedback * link user context to relevant section of the doc * better explain what we achieve with cleaning the cookies --- cookbook/cache/varnish.rst | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/cookbook/cache/varnish.rst b/cookbook/cache/varnish.rst index a4e1adb5a1d..fda8d763e4b 100644 --- a/cookbook/cache/varnish.rst +++ b/cookbook/cache/varnish.rst @@ -74,20 +74,22 @@ prevent clients from bypassing the cache. In practice, you will need sessions at least for some parts of the site, e.g. when using forms with :ref:`CSRF Protection `. In this situation, make sure to only start a session when actually needed, and clear the session when it is no -longer needed. +longer needed. Alternatively, you can look into :doc:`../cache/form_csrf_caching`. -.. todo link "CSRF Protection" to https://github.com/symfony/symfony-docs/pull/4141 .. todo link "only start a session when actually needed" to cookbook/session/avoid_session_start once https://github.com/symfony/symfony-docs/pull/4661 is merged Cookies created in Javascript and used only in the frontend, e.g. when using Google analytics are nonetheless sent to the server. These cookies are not relevant for the backend and should not affect the caching decision. Configure -your Varnish cache to `clean the cookies header`_. Unless you changed the -default configuration of PHP, your session cookie has the name PHPSESSID: +your Varnish cache to `clean the cookies header`_. You want to keep the +session cookie, if there is one, and get rid of all other cookies so that pages +are cached if there is no active session. Unless you changed the default +configuration of PHP, your session cookie has the name PHPSESSID: .. code-block:: varnish4 sub vcl_recv { + // Remove all cookies except the session ID. if (req.http.Cookie) { set req.http.Cookie = ";" + req.http.Cookie; set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";"); @@ -96,6 +98,7 @@ default configuration of PHP, your session cookie has the name PHPSESSID: set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", ""); if (req.http.Cookie == "") { + // If there are no more cookies, remove the header to get page cached. remove req.http.Cookie; } } @@ -106,7 +109,7 @@ default configuration of PHP, your session cookie has the name PHPSESSID: If content is not different for every user, but depends on the roles of a user, a solution is to separate the cache per group. This pattern is implemented and explained by the FOSHttpCacheBundle_ under the name - *User Context*. + `User Context`_. Ensure Consistent Caching Behaviour ----------------------------------- @@ -221,3 +224,4 @@ proxy before it has expired, it adds complexity to your caching setup. .. _`Surrogate-Capability Header`: http://www.w3.org/TR/edge-arch .. _`cache invalidation`: http://tools.ietf.org/html/rfc2616#section-13.10 .. _`FOSHttpCacheBundle`: http://foshttpcachebundle.readthedocs.org/ +.. _`User Context`: http://foshttpcachebundle.readthedocs.org/en/latest/features/user-context.html