1.. index::
2   single: session
3
4.. _sessions_chapter:
5
6Sessions
7========
8
9A :term:`session` is a namespace which is valid for some period of continual
10activity that can be used to represent a user's interaction with a web
11application.
12
13This chapter describes how to configure sessions, what session implementations
14:app:`Pyramid` provides out of the box, how to store and retrieve data from
15sessions, and two session-specific features: flash messages, and cross-site
16request forgery attack prevention.
17
18.. index::
19   single: session factory (default)
20
21.. _using_the_default_session_factory:
22
23Using the Default Session Factory
24---------------------------------
25
26In order to use sessions, you must set up a :term:`session factory` during your
27:app:`Pyramid` configuration.
28
29A very basic, insecure sample session factory implementation is provided in the
30:app:`Pyramid` core.  It uses a cookie to store session information.  This
31implementation has the following limitations:
32
33- The session information in the cookies used by this implementation is *not*
34  encrypted, so it can be viewed by anyone with access to the cookie storage of
35  the user's browser or anyone with access to the network along which the
36  cookie travels.
37
38- The maximum number of bytes that are storable in a serialized representation
39  of the session is fewer than 4000.  This is suitable only for very small data
40  sets.
41
42It is digitally signed, however, and thus its data cannot easily be tampered
43with.
44
45You can configure this session factory in your :app:`Pyramid` application by
46using the :meth:`pyramid.config.Configurator.set_session_factory` method.
47
48.. code-block:: python
49   :linenos:
50
51   from pyramid.session import SignedCookieSessionFactory
52   my_session_factory = SignedCookieSessionFactory('itsaseekreet')
53
54   from pyramid.config import Configurator
55   config = Configurator()
56   config.set_session_factory(my_session_factory)
57
58.. warning::
59
60   By default the :func:`~pyramid.session.SignedCookieSessionFactory`
61   implementation is *unencrypted*.  You should not use it when you keep
62   sensitive information in the session object, as the information can be
63   easily read by both users of your application and third parties who have
64   access to your users' network traffic.  And, if you use this sessioning
65   implementation, and you inadvertently create a cross-site scripting
66   vulnerability in your application, because the session data is stored
67   unencrypted in a cookie, it will also be easier for evildoers to obtain the
68   current user's cross-site scripting token.  In short, use a different
69   session factory implementation (preferably one which keeps session data on
70   the server) for anything but the most basic of applications where "session
71   security doesn't matter", and you are sure your application has no
72   cross-site scripting vulnerabilities.
73
74.. index::
75   single: session object
76
77Using a Session Object
78----------------------
79
80Once a session factory has been configured for your application, you can access
81session objects provided by the session factory via the ``session`` attribute
82of any :term:`request` object.  For example:
83
84.. code-block:: python
85   :linenos:
86
87   from pyramid.response import Response
88
89   def myview(request):
90       session = request.session
91       if 'abc' in session:
92           session['fred'] = 'yes'
93       session['abc'] = '123'
94       if 'fred' in session:
95           return Response('Fred was in the session')
96       else:
97           return Response('Fred was not in the session')
98
99The first time this view is invoked produces ``Fred was not in the session``.
100Subsequent invocations produce ``Fred was in the session``, assuming of course
101that the client side maintains the session's identity across multiple requests.
102
103You can use a session much like a Python dictionary.  It supports all
104dictionary methods, along with some extra attributes and methods.
105
106Extra attributes:
107
108``created``
109  An integer timestamp indicating the time that this session was created.
110
111``new``
112  A boolean.  If ``new`` is True, this session is new.  Otherwise, it has been
113  constituted from data that was already serialized.
114
115Extra methods:
116
117``changed()``
118  Call this when you mutate a mutable value in the session namespace. See the
119  gotchas below for details on when and why you should call this.
120
121``invalidate()``
122  Call this when you want to invalidate the session (dump all data, and perhaps
123  set a clearing cookie).
124
125The formal definition of the methods and attributes supported by the session
126object are in the :class:`pyramid.interfaces.ISession` documentation.
127
128Some gotchas:
129
130- Keys and values of session data must be *pickleable*.  This means, typically,
131  that they are instances of basic types of objects, such as strings, lists,
132  dictionaries, tuples, integers, etc.  If you place an object in a session
133  data key or value that is not pickleable, an error will be raised when the
134  session is serialized.
135
136- If you place a mutable value (for example, a list or a dictionary) in a
137  session object, and you subsequently mutate that value, you must call the
138  ``changed()`` method of the session object. In this case, the session has no
139  way to know that it was modified.  However, when you modify a session object
140  directly, such as setting a value (i.e., ``__setitem__``), or removing a key
141  (e.g., ``del`` or ``pop``), the session will automatically know that it needs
142  to re-serialize its data, thus calling ``changed()`` is unnecessary. There is
143  no harm in calling ``changed()`` in either case, so when in doubt, call it
144  after you've changed sessioning data.
145
146.. index::
147   single: pyramid_redis_sessions
148   single: session factory (alternates)
149
150.. _using_alternate_session_factories:
151
152Using Alternate Session Factories
153---------------------------------
154
155The following session factories exist at the time of this writing.
156
157======================= ======= =============================
158Session Factory         Backend   Description
159======================= ======= =============================
160pyramid_redis_sessions_ Redis_  Server-side session library
161                                for Pyramid, using Redis for
162                                storage.
163pyramid_beaker_         Beaker_ Session factory for Pyramid
164                                backed by the Beaker
165                                sessioning system.
166======================= ======= =============================
167
168.. _pyramid_redis_sessions: https://pypi.python.org/pypi/pyramid_redis_sessions
169.. _Redis: http://redis.io/
170
171.. _pyramid_beaker: https://pypi.python.org/pypi/pyramid_beaker
172.. _Beaker: http://beaker.readthedocs.org/en/latest/
173
174.. index::
175   single: session factory (custom)
176
177Creating Your Own Session Factory
178---------------------------------
179
180If none of the default or otherwise available sessioning implementations for
181:app:`Pyramid` suit you, you may create your own session object by implementing
182a :term:`session factory`.  Your session factory should return a
183:term:`session`.  The interfaces for both types are available in
184:class:`pyramid.interfaces.ISessionFactory` and
185:class:`pyramid.interfaces.ISession`. You might use the cookie implementation
186in the :mod:`pyramid.session` module as inspiration.
187
188.. index::
189   single: flash messages
190
191.. _flash_messages:
192
193Flash Messages
194--------------
195
196"Flash messages" are simply a queue of message strings stored in the
197:term:`session`.  To use flash messaging, you must enable a :term:`session
198factory` as described in :ref:`using_the_default_session_factory` or
199:ref:`using_alternate_session_factories`.
200
201Flash messaging has two main uses: to display a status message only once to the
202user after performing an internal redirect, and to allow generic code to log
203messages for single-time display without having direct access to an HTML
204template. The user interface consists of a number of methods of the
205:term:`session` object.
206
207.. index::
208   single: session.flash
209
210Using the ``session.flash`` Method
211~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
212
213To add a message to a flash message queue, use a session object's ``flash()``
214method:
215
216.. code-block:: python
217
218   request.session.flash('mymessage')
219
220The ``flash()`` method appends a message to a flash queue, creating the queue
221if necessary.
222
223``flash()`` accepts three arguments:
224
225.. method:: flash(message, queue='', allow_duplicate=True)
226
227The ``message`` argument is required.  It represents a message you wish to
228later display to a user.  It is usually a string but the ``message`` you
229provide is not modified in any way.
230
231The ``queue`` argument allows you to choose a queue to which to append the
232message you provide.  This can be used to push different kinds of messages into
233flash storage for later display in different places on a page.  You can pass
234any name for your queue, but it must be a string. Each queue is independent,
235and can be popped by ``pop_flash()`` or examined via ``peek_flash()``
236separately.  ``queue`` defaults to the empty string.  The empty string
237represents the default flash message queue.
238
239.. code-block:: python
240
241   request.session.flash(msg, 'myappsqueue')
242
243The ``allow_duplicate`` argument defaults to ``True``.  If this is ``False``,
244and you attempt to add a message value which is already present in the queue,
245it will not be added.
246
247.. index::
248   single: session.pop_flash
249
250Using the ``session.pop_flash`` Method
251~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
252
253Once one or more messages have been added to a flash queue by the
254``session.flash()`` API, the ``session.pop_flash()`` API can be used to pop an
255entire queue and return it for use.
256
257To pop a particular queue of messages from the flash object, use the session
258object's ``pop_flash()`` method. This returns a list of the messages that were
259added to the flash queue, and empties the queue.
260
261.. method:: pop_flash(queue='')
262
263.. testsetup::
264
265   from pyramid import testing
266   request = testing.DummyRequest()
267
268.. doctest::
269
270   >>> request.session.flash('info message')
271   >>> request.session.pop_flash()
272   ['info message']
273
274Calling ``session.pop_flash()`` again like above without a corresponding call
275to ``session.flash()`` will return an empty list, because the queue has already
276been popped.
277
278.. doctest::
279
280   >>> request.session.flash('info message')
281   >>> request.session.pop_flash()
282   ['info message']
283   >>> request.session.pop_flash()
284   []
285
286.. index::
287   single: session.peek_flash
288
289Using the ``session.peek_flash`` Method
290~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
291
292Once one or more messages have been added to a flash queue by the
293``session.flash()`` API, the ``session.peek_flash()`` API can be used to "peek"
294at that queue.  Unlike ``session.pop_flash()``, the queue is not popped from
295flash storage.
296
297.. method:: peek_flash(queue='')
298
299.. doctest::
300
301   >>> request.session.flash('info message')
302   >>> request.session.peek_flash()
303   ['info message']
304   >>> request.session.peek_flash()
305   ['info message']
306   >>> request.session.pop_flash()
307   ['info message']
308   >>> request.session.peek_flash()
309   []
310
311.. index::
312   single: preventing cross-site request forgery attacks
313   single: cross-site request forgery attacks, prevention
314
315Preventing Cross-Site Request Forgery Attacks
316---------------------------------------------
317
318`Cross-site request forgery
319<https://en.wikipedia.org/wiki/Cross-site_request_forgery>`_ attacks are a
320phenomenon whereby a user who is logged in to your website might inadvertantly
321load a URL because it is linked from, or embedded in, an attacker's website.
322If the URL is one that may modify or delete data, the consequences can be dire.
323
324You can avoid most of these attacks by issuing a unique token to the browser
325and then requiring that it be present in all potentially unsafe requests.
326:app:`Pyramid` sessions provide facilities to create and check CSRF tokens.
327
328To use CSRF tokens, you must first enable a :term:`session factory` as
329described in :ref:`using_the_default_session_factory` or
330:ref:`using_alternate_session_factories`.
331
332.. index::
333   single: session.get_csrf_token
334
335Using the ``session.get_csrf_token`` Method
336~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
337
338To get the current CSRF token from the session, use the
339``session.get_csrf_token()`` method.
340
341.. code-block:: python
342
343   token = request.session.get_csrf_token()
344
345The ``session.get_csrf_token()`` method accepts no arguments.  It returns a
346CSRF *token* string. If ``session.get_csrf_token()`` or
347``session.new_csrf_token()`` was invoked previously for this session, then the
348existing token will be returned.  If no CSRF token previously existed for this
349session, then a new token will be set into the session and returned.  The newly
350created token will be opaque and randomized.
351
352You can use the returned token as the value of a hidden field in a form that
353posts to a method that requires elevated privileges, or supply it as a request
354header in AJAX requests.
355
356For example, include the CSRF token as a hidden field:
357
358.. code-block:: html
359
360    <form method="post" action="/myview">
361      <input type="hidden" name="csrf_token" value="${request.session.get_csrf_token()}">
362      <input type="submit" value="Delete Everything">
363    </form>
364
365Or include it as a header in a jQuery AJAX request:
366
367.. code-block:: javascript
368
369    var csrfToken = ${request.session.get_csrf_token()};
370    $.ajax({
371      type: "POST",
372      url: "/myview",
373      headers: { 'X-CSRF-Token': csrfToken }
374    }).done(function() {
375      alert("Deleted");
376    });
377
378The handler for the URL that receives the request should then require that the
379correct CSRF token is supplied.
380
381.. index::
382   single: session.new_csrf_token
383
384Using the ``session.new_csrf_token`` Method
385~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
386
387To explicitly create a new CSRF token, use the ``session.new_csrf_token()``
388method.  This differs only from ``session.get_csrf_token()`` inasmuch as it
389clears any existing CSRF token, creates a new CSRF token, sets the token into
390the session, and returns the token.
391
392.. code-block:: python
393
394   token = request.session.new_csrf_token()
395
396Checking CSRF Tokens Manually
397~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
398
399In request handling code, you can check the presence and validity of a CSRF
400token with :func:`pyramid.session.check_csrf_token`. If the token is valid, it
401will return ``True``, otherwise it will raise ``HTTPBadRequest``. Optionally,
402you can specify ``raises=False`` to have the check return ``False`` instead of
403raising an exception.
404
405By default, it checks for a POST parameter named ``csrf_token`` or a header
406named ``X-CSRF-Token``.
407
408.. code-block:: python
409
410   from pyramid.session import check_csrf_token
411
412   def myview(request):
413       # Require CSRF Token
414       check_csrf_token(request)
415
416       # ...
417
418.. _auto_csrf_checking:
419
420Checking CSRF Tokens Automatically
421~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
422
423.. versionadded:: 1.7
424
425:app:`Pyramid` supports automatically checking CSRF tokens on requests with an
426unsafe method as defined by RFC2616. Any other request may be checked manually.
427This feature can be turned on globally for an application using the
428:meth:`pyramid.config.Configurator.set_default_csrf_options` directive.
429For example:
430
431.. code-block:: python
432
433   from pyramid.config import Configurator
434
435   config = Configurator()
436   config.set_default_csrf_options(require_csrf=True)
437
438CSRF checking may be explicitly enabled or disabled on a per-view basis using
439the ``require_csrf`` view option. A value of ``True`` or ``False`` will
440override the default set by ``set_default_csrf_options``. For example:
441
442.. code-block:: python
443
444   @view_config(route_name='hello', require_csrf=False)
445   def myview(request):
446       # ...
447
448When CSRF checking is active, the token and header used to find the
449supplied CSRF token will be ``csrf_token`` and ``X-CSRF-Token``, respectively,
450unless otherwise overridden by ``set_default_csrf_options``. The token is
451checked against the value in ``request.POST`` which is the submitted form body.
452If this value is not present, then the header will be checked.
453
454In addition to token based CSRF checks, if the request is using HTTPS then the
455automatic CSRF checking will also check the referrer of the request to ensure
456that it matches one of the trusted origins. By default the only trusted origin
457is the current host, however additional origins may be configured by setting
458``pyramid.csrf_trusted_origins`` to a list of domain names (and ports if they
459are non standard). If a host in the list of domains starts with a ``.`` then
460that will allow all subdomains as well as the domain without the ``.``.
461
462If CSRF checks fail then a :class:`pyramid.exceptions.BadCSRFToken` or
463:class:`pyramid.exceptions.BadCSRFOrigin` exception will be raised. This
464exception may be caught and handled by an :term:`exception view` but, by
465default, will result in a ``400 Bad Request`` response being sent to the
466client.
467
468Checking CSRF Tokens with a View Predicate
469~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
470
471.. deprecated:: 1.7
472   Use the ``require_csrf`` option or read :ref:`auto_csrf_checking` instead
473   to have :class:`pyramid.exceptions.BadCSRFToken` exceptions raised.
474
475A convenient way to require a valid CSRF token for a particular view is to
476include ``check_csrf=True`` as a view predicate. See
477:meth:`pyramid.config.Configurator.add_view`.
478
479.. code-block:: python
480
481    @view_config(request_method='POST', check_csrf=True, ...)
482    def myview(request):
483        ...
484
485.. note::
486   A mismatch of a CSRF token is treated like any other predicate miss, and the
487   predicate system, when it doesn't find a view, raises ``HTTPNotFound``
488   instead of ``HTTPBadRequest``, so ``check_csrf=True`` behavior is different
489   from calling :func:`pyramid.session.check_csrf_token`.
490