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.

44 comments

  • #26 Miguel Grinberg said 2017-11-28T19:45:38Z

    Server side sessions come in many flavors, so the security of the data depends on what session storage you use. I recommend that you do not store user passwords if possible. You can store a password hash to make it more secure, or if you really need to have the actual password, then encrypt it and store the encryption key somewhere else.

  • #27 Will said 2018-01-12T23:23:19Z

    Hi, I'm fairly new to Flask, but I like it so far. I am using the method described in the flask.pocoo.org quickstart guide: from flask import Flask, session, redirect, url_for, escape, request app = Flask(__name__) @app.route('/') def index(): if 'username' in session: return 'Logged in as %s' % escape(session['username']) return 'You are not logged in' Anyways, this works great, but I am concerned with the following. If I login, I can copy the value of the cookie (Everything, including the periods and the characters around them), bring that value to another computer, and insert them into a newly created cookie on that computer. Then I am logged in. 1) Is this a legit use case, or is this hard for attackers to do? Obviously not copy and paste, but to get access to that cookie value... 2) Do you know of any good ways prevent this? Thanks!

  • #28 Miguel Grinberg said 2018-01-12T23:42:50Z

    @Will: you are correct, if you take a session cookie from machine A and put it machine B, then machine B will appear as logged in. Browsers have options to try to prevent cookies from being stolen, so as long as you understand them and use them appropriately, your risk decreases considerably. I have written about the topic in this blog: https://blog.miguelgrinberg.com/post/cookie-security-for-flask-applications, and have also created a Flask extension called Flask-Paranoid that helps protect your user session cookies.

  • #29 Tim said 2018-07-16T17:50:42Z

    Any idea on why Flask stores the entire session payload in cookies? Everyone else just stores a unique key to lookup the relevant information that's stored on the server and never sends the actual session contents to the server. This seems to be a good way to accidentally expose people's secrets since Flask's behavior is completely opposite of what's normally expected, isn't it?

  • #30 Miguel Grinberg said 2018-07-17T05:54:33Z

    @Tim: I believe you are wrong, Flask isn't the only framework that does this, and it is certainly not the first. For example, Django has them too. Cookie based user sessions are convenient and highly scalable, as they require no server-side storage, so they are appropriate for a lot of applications. The Flask-Session extension provides additional session backends, so you can work with user session that are stored in the server if you prefer that model.

  • #31 Pritish Sehzpaul said 2019-02-25T20:38:01Z

    Hi Miguel, So, I followed the steps and decrypted the cookie but could find nothing readable. It was encrypted and seemed pretty random with the value in hexadecimals. I believe you should update your article or archive this one so that we could mark it as a learning phase for Flask's history. Cheers :)

  • #32 Miguel Grinberg said 2019-02-25T22:08:37Z

    @Pritish: this is still valid, as far as I know. Maybe you've got a compressed cookie. This is covered in the video.

  • #33 Fernando A. said 2019-03-20T21:54:26Z

    Hello, I was reading this article and seems fantastic. I have an issue in my code since I change to Flask-Session. When I try to perform a request through a form I receive an error which says: {'csrf_token': ['The CSRF session token is missing.']} and no, the token is there in the HTML form. And when I try to check in the cookie session I have a value that I cannot decrypt as you show in your video, because is generated by Flask-Session. What is the issue with that??

  • #34 Miguel Grinberg said 2019-03-20T22:41:03Z

    @Fernando: are you sure Flask-Session is working? If the session isn't being written correctly then the CSRF token will change every time. That's the only theory I can offer, your user session isn't being written so your session is reset with every request.

  • #35 Fernando A. said 2019-03-22T16:35:21Z

    @Miguel thanks you very much for answer me, let me explain well. When I run the app in my computer work perfectly every time, and I can have many requests at the same time form different computers. But when I try to run in my Rasberry Pi using UWSGI server I can't handle multiples request from multiples computers. So make me wonders if this is the server's fault?

  • #36 Fernando A. said 2019-03-22T16:41:11Z

    And another thing, I was looking now your conference "Miguel Grinberg - Microservices with Python and Flask - PyCon 2017" and one thing isn't clear for me. What is best for Tokens Flask-Sessions or JWT?? or is possible to use both?? Because when I use JWT is very easy to decrypt the information using this video tutorial, that means that I have to provide the info already encrypted in the token?? **Currently I'm using Flask-Session with SQLAlchemy Thanks a lot and sorry for so many questions

  • #37 Miguel Grinberg said 2019-03-22T22:32:20Z

    @Fernando: How many worker process are you running with uWSGI? It's likely a configuration problem. Set it to use four workers and you'll be able to handle four requests concurrently. Can't really say what's best on server or client-side sessions, it depends on each case. Sessions aren't supposed to hold sensitive information, so they normally don't need encryption. If you are storing sensitive data you should use server-side sessions.

  • #38 Fernando A. said 2019-03-23T18:35:45Z

    Thank you so much for the help, I set uWSGI server with 4 workers and is working very well.

  • #39 Fernando A. said 2019-03-23T18:39:19Z

    Talking about Microservices, there is one thing that isn't clear for me. If each microservices has his own database, and for example, one microservice is down, his database's information isn't' available no?? And what happens if I need that information, how could I have it?? Or if the microservice is killed, the database with the data is killed too??

  • #40 Miguel Grinberg said 2019-03-23T20:37:40Z

    @Fernando: you normally run more than one instance of each microservice. If a microservice dies, another instance takes over until the dead one can be restarted.

  • #41 Edon said 2019-06-11T09:05:07Z

    Hello, coming back to this, I've found out that if you use nginx reverse-proxy, I'm unable to decode base64 cookies, it appears to be encrypted. Anyone familiar with this?

  • #42 Miguel Grinberg said 2019-06-11T09:57:42Z

    @Edon: Did you see the part about compressed cookies? My guess is that you now have one that is compressed, not encrypted.

  • #43 mohamed said 2019-12-21T12:51:23Z

    Hi, I would just like to know, is it the same case today that flask sessions are not secure? I noticed that this post is 4 years old.

  • #44 Miguel Grinberg said 2019-12-21T16:28:34Z

    @mohamed: I think you did not understand what I was trying to convey in this post. The Flask sessions are 100% secure, they were secure 4 years ago and continue to be secure today. What they are not is encrypted, which has nothing to do with being secure or not.

Leave a Comment