Flask - Cookie and Token Sessions Simultaneously

Dealing with sessions in Flask applications is rather simple! There is plenty of choice in pre-rolled implementations that is more or less plug-and-play.

However, sometimes you may want (or need) to colour outside the lines, where a cookie-cutter implentation either doesn’t work, or gets in the way.

One such scenario is if you have an application which needs to act both as a web front-end, with your typical cookie-based sessions, as well as an API endpoint. Requiring cookies when you’re acting as an API endpoint isn’t particularly nice, tokens in the request header is the way to go! So how can you get Flask sessions to work with both these methods of identification?

Perhaps at this point, I should add that you might be best served by reconsidering your strategy here, and make the API endpoint a distinct application from the one driving your UI. You can still share all your code for your models and logic and can even make use of a layer 7 load balancer to deal with the separation for you. But be it due to retrofitting, time constraint, legacy or otherwise imposed design.. here goes;

Since Flask is a pretty lightweight framework, it’s easily extended or wrestled into submission. Luckily for us, it offers a pluggable way to write your own session handling!

I’ve put a small example application with a custom session interface on GitHub, which allows what we’ve previously discussed. You can either distinguish sessions by a cookie, if present, or a header of your chosing (cookie trumps header, if both are present). This header defaults to the de-facto standard X-Auth-Header in the example, but you can configure this easily. For ease of use, the datastore used to store the sessions is memcached. But it’s very easily replaced by any other datastore.

The example is as small and compact as possible while remaining runnable. There are no “bells and whistles” such as actual authentication, that’s for you to handle outside of the session handler. You will also most likely want to extend the error checking and handling.

Do note - there’s a docker-compose file included in the repository, which will enable you to quickly get up and running. Alternatively you can simply run pip install -r requirements.txt && ./runserver.py from within the app/ directory, provided that you have the required system dependencies.

Here’s an example of using this session handler with cookies:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ docker-compose up -d
# -d optional, leave it off to run in the foreground

$ http http://localhost:9000
HTTP/1.0 200 OK
Content-Length: 64
Content-Type: text/html; charset=utf-8
Date: Fri, 06 Jan 2017 20:22:38 GMT
Server: Werkzeug/0.11.15 Python/2.7.6
Set-Cookie: session=e53941b4-dc32-4e30-902a-a197cd1140b5; Expires=Fri,
06-Jan-2017 20:23:08 GMT; HttpOnly; Path=/

The random identifier stored with your session is: 05ed02a2-48ef-4c5a-8588-9a87356ddad9

$ http -b http://localhost:9000 Cookie:session=e53941b4-dc32-4e30-902a-a197cd1140b5 
The random identifier stored with your session is: 05ed02a2-48ef-4c5a-8588-9a87356ddad9

Since we don’t send a JSON body containing the key token, or set the X-Auth-Token header, the session handler determines the application should send a cookie.

The example has a session timeout of a mighty 30 seconds (configurable, obviously).

Now, if we were to behave like an API, on the other hand:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ echo '{ "token": "pretend_token"}' | http  --json POST http://localhost:9000
HTTP/1.0 200 OK
Content-Length: 64
Content-Type: text/html; charset=utf-8
Date: Fri, 06 Jan 2017 17:56:33 GMT
Server: Werkzeug/0.11.15 Python/2.7.6

The random identifier stored with your session is: d4945e3e-21bc-42db-9b1b-a0c941a25ddb

$ http -b http://localhost:9000 x-auth-token:pretend_token
The random identifier stored with your session is: d4945e3e-21bc-42db-9b1b-a0c941a25ddb

$ sleep 30
$ http -b http://localhost:9000 x-auth-token:pretend_token
The random identifier stored with your session is: 7095b0eb-1efa-4b75-b9e2-a02c7f6e837b

As you can see, we don’t get a cookie sent back, because we behaved like an API client. We can also see that we get a brand new session after the 30 seconds has elapsed.

The example also comes with a test suite for verification. You can execute this by simply running make tests:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ make tests
docker-compose exec session_example /bin/bash -c "cd /app ; python -m unittest discover -s tests/"
Previously unseen session... Setting identifier
Previously unseen session... Setting identifier
.Previously unseen session... Setting identifier
Previously unseen session... Setting identifier
..Previously unseen session... Setting identifier
...Previously unseen session... Setting identifier
...
----------------------------------------------------------------------
Ran 9 tests in 0.027s

OK

The tests all run in a docker container, so the first time you run it, you’ll most likely see an image being built, and a memcached image being pulled.

Hope this helps someone!

Jan 6th, 2017