2016-04-04T17:57:45Z

How Secure Is The Flask User Session?

Many times I hear people say that user sessions in Flask are encrypted, so it is safe to write private information in them. Sadly, this is a misconception that can have catastrophic consequences for your applications and, most importantly, for your users. Don't believe me? Below you can watch me decode a Flask user session in just a few seconds, without needing the application's secret key that was used to encode it.

So never, ever store secrets in a Flask session cookie. You can, however, store information that is not a secret, but needs to be stored securely. I hope the video makes it clear that while the data in the user session is easily accessible, there is very good protection against tampering, as long as the server's secret key is not compromised.

The Example Application

Want to play with sessions using the "Guess the Number" application I created for the video demonstration? No problem, below you can find the code and templates. As you know, I always put my examples on github, but since this is an example of how not to do things, I am reluctant to have this in there, where people can see it out of context and misunderstand it. So I'm sorry, but for this example, you are going to have to copy and paste the four files.

The application is in file guess.py:

import os
import random
from flask import Flask, session, redirect, url_for, request, render_template

app = Flask(__name__)
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY') or \
    'e5ac358c-f0bf-11e5-9e39-d3b532c10a28'

@app.route('/')
def index():
    # the "answer" value cannot be stored in the user session as done below
    # since the session is sent to the client in a cookie that is not encrypted!
    session['answer'] = random.randint(1, 10)
    session['try_number'] = 1
    return redirect(url_for('guess'))

@app.route('/guess')
def guess():
    guess = int(request.args['guess']) if 'guess' in request.args else None
    if request.args.get('guess'):
        if guess == session['answer']:
            return render_template('win.html')
        else:
            session['try_number'] += 1
            if session['try_number'] > 3:
                return render_template('lose.html', guess=guess)
    return render_template('guess.html', try_number=session['try_number'],
                           guess=guess)

if __name__ == '__main__':
    app.run()

There are also three template files that you will need to store in a templates subdirectory. Template number one is called guess.html:

<html>
    <head>
        <title>Guess the number!</title>
    </head>
    <body>
        <h1>Guess the number!</h1>
        {% if try_number == 1 %}
        <p>I thought of a number from 1 to 10. Can you guess it?</p>
        {% else %}
        <p>Sorry, {{ guess }} is incorrect. Try again!</p>
        {% endif %}
        <form action="">
            Try #{{ try_number }}: <input type="text" name="guess">
            <input type="submit">
        </form>
    </body>
</html>

Template number two is win.html:

<html>
    <head>
        <title>Guess the number: You win!</title>
    </head>
    <body>
        <h1>Guess the number!</h1>
        <p>Congratulations, {{ session['answer'] }} is the correct number.</p>
        <p><a href="{{ url_for('index') }}">Play again</a></p>
    </body>
</html>

And the last template is lose.html:

<html>
    <head>
        <title>Guess the number: You lose!</title>
    </head>
    <body>
        <h1>Guess the number!</h1>
        <p>Sorry, {{ guess }} is incorrect. My guess was {{ session['answer'] }}.</p>
        <p><a href="{{ url_for('index') }}">Play again</a></p>
    </body>
</html>

If Sessions Aren't Secure Enough, Then What Is?

Now you know that the Flask user session is not the right place to store sensitive information. But what do you do if you have secrets that need to be stored, and using a database seems overkill?

One very good solution is to use a different user session implementation. Flask uses cookie based sessions by default, but there is support for custom sessions that store data in other places. In particular, the Flask-Session extension is very interesting, as it stores the user session data in the server, giving you a variety of storage options such as plain files, Redis, relational databases, etc. When the session data is stored in the server you can be sure that any data that you write to it is as secure as your server.

11 comments

  • #1 Usman Ehtesham Gul said 2016-04-05T22:06:02Z

    What is the best way to store the secret key in production servers? I store them as an environment variable? Any recommendations for something better?

  • #2 Miguel Grinberg said 2016-04-06T19:35:49Z

    @Usman: there are lots of ways to store secrets. You can store them encrypted in your database (though you then need a place to store the encryption key...), you can use a secrets database such as Vault, if you run on AWS you can store them in a protected S3 bucket, there's really many places besides the environment. But at the end of the day, your server needs to have a way to obtain these secrets, so in my opinion, it makes more sense to put effort in protecting the server against unauthorized access than finding clever ways to store your secrets.

  • #3 Mike said 2016-04-23T17:31:01Z

    I really enjoyed the article Miguel! I don't think i've ever used the "vanilla" flask sessions. I particularly like using redis which is very easy to implement and add to a configuration management task (like ansible). This flask snippet has worked wonders for me: http://flask.pocoo.org/snippets/75/.

  • #4 Abhay Godbole said 2016-05-20T16:50:41Z

    Hi Miguel, I have just started with Python and Flask and you are my first Mentor. I have watched your Pycon videos and reading your excellent Book. I am working on a product in a financial domain where we would be using IBM Watson services using Python and Flask. This article is very good, but I need to read it again when I get more control on Flask. My question is not related to this article, hope it is ok to ask here. I want to handle multiple forms in a single view. I have one page where I have 3 Tabs and each will have different forms. Currently I am having one route mapped to html in which I have this 3 Tabs. It looks bit tricky to me. Please give me some inputs to handle this scenario. Please get back as per your convenience. Thanks & Regards Abhay

  • #5 Miguel Grinberg said 2016-05-22T00:38:06Z

    @abhay: this will give you some ideas: http://stackoverflow.com/questions/21949452/wtforms-two-forms-on-the-same-page

  • #6 Abhay said 2016-05-22T06:49:59Z

    Thanks Miguel... This will help me. I will try on this, if I face any issue, will get back

  • #7 Iron Fist said 2016-06-19T02:05:06Z

    Interesting article, I always had in my mind the question about how secure are the session cookies since in Flask'docs[1] they mentioned: " What this means is that the user could look at the contents of your cookie but not modify it, unless they know the secret key used for signing." <a href="http://flask.pocoo.org/docs/0.11/quickstart/#sessions">[1]</a>

  • #8 Aris said 2016-08-12T04:31:24Z

    Hello Miguel, I was a little bit confused, in the example code, what does the " 'answer' value cannot be stored in the user session as done below since the session is sent to the client in a cookie that is not encrypted!" actually mean? Thanks in advance.

  • #9 Miguel Grinberg said 2016-08-16T19:47:14Z

    @Aris: that's just so that it is clear that the implementation is completely wrong. I don't want you to see that code and think it is okay to copy/paste it. The idea is that if you have any information you don't want your clients to discover, then you cannot put it in the user session, since that is unencrypted.

  • #10 senaps said 2016-11-28T21:43:00Z

    so, based on what we see, i can save the user state(loged_in) and they can see it, but can't change it anyway... am i safe to do that?

  • #11 Miguel Grinberg said 2016-11-28T22:24:26Z

    @senaps: Correct. You can save a user id or any other public way to identify a user and a logged in state. These can be seen by anyone, but they cannot be changed (as long as you keep your Flask secret key secret).

Leave a Comment