- Fix case duplicates (guide→Guide, project→Project, etc.) - Consolidate single-post tags into broader categories - Add technology tags (Python, AWS, Salt, Docker, Kubernetes) to 93 posts - Add lib/tag_stats.py tool for checking tag usage - Update CLAUDE.md with tag guidelines
168 lines
5.2 KiB
ReStructuredText
168 lines
5.2 KiB
ReStructuredText
Sharing a Pyramid cookie with Flask or Tornado
|
|
################################################################
|
|
|
|
:author: Russell Ballestrini
|
|
:slug: sharing-a-pyramid-cookie-with-flask-or-tornado
|
|
:date: 2016-11-21 18:35
|
|
:tags: Code, Python, Salt
|
|
:status: published
|
|
|
|
Do you have a Pyramid application which authenticates users and uses a signed cookie as a session?
|
|
Do you want to build a microservice using another framework and allow it to use the same cookie and session? Me too!
|
|
|
|
First we will review a bit of Pyramid code which describes the cookie session.
|
|
|
|
Setup Pyramid Signed Cookie Session
|
|
====================================
|
|
|
|
At the top of your ``__init__.py`` you will have the following import:
|
|
|
|
.. code-block:: python
|
|
|
|
# cookie only session, not encrypted but signed to prevent tampering!
|
|
from pyramid.session import SignedCookieSessionFactory
|
|
|
|
In the ``main()`` function of ``__init__.py`` you will create a ``Configurator``:
|
|
|
|
.. code-block:: python
|
|
|
|
def main(global_config, **settings):
|
|
""" This function returns a Pyramid WSGI application."""
|
|
|
|
# setup session factory to use unencrypted but signed cookies.
|
|
session_factory = SignedCookieSessionFactory(
|
|
secret = 'test-secret'
|
|
)
|
|
|
|
# build app config object from ini.
|
|
config = Configurator(
|
|
settings = settings,
|
|
session_factory = session_factory,
|
|
)
|
|
|
|
As you can see for this test example the ``secret`` is hardcoded. A real application would define the secret in the .ini and access it using the ``settings`` object.
|
|
|
|
Now all new requests will have a ``request.session`` attribute.
|
|
|
|
You use it like a dictionary, for example:
|
|
|
|
.. code-block:: python
|
|
|
|
# set some data into the cookie. for me, this logs the user in.
|
|
request.session['authenticated_user_id'] = 8
|
|
|
|
# get some data from the cookie. for me this gets the user_id or None.
|
|
authenticated_user_id = request.session.get('authenticated_user_id', None)
|
|
|
|
# delete a key / value from cookie. for me this will log out the user.
|
|
request.session['authenticated_user_id'] = None
|
|
|
|
Reference: `SignedCookieSessionFactory <https://docs.pylonsproject.org/projects/pyramid/en/latest/api/session.html#pyramid.session.SignedCookieSessionFactory>`_
|
|
|
|
|
|
|
|
Teach Tornado how to read Pyramid cookies
|
|
================================================
|
|
|
|
In this section I'll show you how to access and deserialize the Pyramid cookie from a Tornado application.
|
|
|
|
To do this, I'm going to extend the Tornado ``Hello, world`` application:
|
|
|
|
.. code-block:: python
|
|
|
|
import tornado.ioloop
|
|
import tornado.web
|
|
|
|
class MainHandler(tornado.web.RequestHandler):
|
|
def get(self):
|
|
self.write("Hello, world")
|
|
|
|
def make_app():
|
|
return tornado.web.Application([
|
|
(r"/", MainHandler),
|
|
])
|
|
|
|
if __name__ == "__main__":
|
|
app = make_app()
|
|
app.listen(8888)
|
|
tornado.ioloop.IOLoop.current().start()
|
|
|
|
This is a simple application which listens to port 8888 and serves the text ``Hello, world`` when ``/`` is requested.
|
|
|
|
Add the following imports:
|
|
|
|
.. code-block:: python
|
|
|
|
# accessing Pyramid cookies.
|
|
from webob.cookies import SignedSerializer
|
|
from pyramid.session import PickleSerializer
|
|
from pyramid.compat import bytes_
|
|
|
|
For testing purposes, create a global ``serializer`` object:
|
|
|
|
.. code-block:: python
|
|
|
|
# https://docs.webob.org/en/stable/api/cookies.html#webob.cookies.SignedSerializer
|
|
serializer = SignedSerializer(secret='test-secret', salt='pyramid.session.', serializer=PickleSerializer())
|
|
|
|
Adjust the ``get`` method in the ``MainHandler`` to look like this:
|
|
|
|
.. code-block:: python
|
|
|
|
def get(self):
|
|
raw_cookie = self.get_cookie('session', None)
|
|
if raw_cookie is not None:
|
|
session_data = serializer.loads(bytes_(raw_cookie))
|
|
self.write(str(session_data))
|
|
self.write(str("<br/>"))
|
|
self.write("Hello, world")
|
|
|
|
|
|
The complete program follows:
|
|
|
|
.. code-block:: python
|
|
|
|
import tornado.ioloop
|
|
import tornado.web
|
|
|
|
# accessing Pyramid cookies.
|
|
from webob.cookies import SignedSerializer
|
|
from pyramid.session import PickleSerializer
|
|
from pyramid.compat import bytes_
|
|
|
|
# https://docs.webob.org/en/stable/api/cookies.html#webob.cookies.SignedSerializer
|
|
serializer = SignedSerializer(secret='test-secret', salt='pyramid.session.', serializer=PickleSerializer())
|
|
|
|
class MainHandler(tornado.web.RequestHandler):
|
|
def get(self):
|
|
raw_cookie = self.get_cookie('session', None)
|
|
if raw_cookie is not None:
|
|
session_data = serializer.loads(bytes_(raw_cookie))
|
|
self.write(str(session_data))
|
|
self.write(str("<br/>"))
|
|
self.write("Hello, world")
|
|
|
|
|
|
def make_app():
|
|
return tornado.web.Application([
|
|
(r"/", MainHandler),
|
|
])
|
|
|
|
if __name__ == "__main__":
|
|
app = make_app()
|
|
app.listen(8888)
|
|
tornado.ioloop.IOLoop.current().start()
|
|
|
|
|
|
Again, we are hardcoding the same ``secret``. If you set everything up properly, loading ``http://127.0.0.1:8888`` in a web browser should print the cookie session_data in plain-text.
|
|
|
|
In my testing, I saw my cookie and it looked like this:
|
|
|
|
.. code-block:: python
|
|
|
|
(1479520270, 1479516714.062414, {'authenticated_user_id': 5, 'nodes_pending_verify': []})
|
|
Hello, world
|
|
|
|
Thats all for now, let me know what you think in the comments!
|
|
|
|
.. contents::
|