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