When building applications that handle user accounts, a lot of functionality depends on storing session variables for users. Consider a typical checkout cart: abandoning an e-commerce cart mid-checkout will often retain its contents after a user abandons the flow, only to return later to find their cart remains populated.

Retaining aspects of users' sessions results in an overall better experience. Users have generally come to expect that actions they complete will not be lost if they accidentally (or intentionally) end their "session." Accidental logouts, multitasking, and OS crashes are a part of everyday life. Users who return to your application to find their progress is not lost is the difference between user retention and user abandon.

Flask's default method of storing user session values via cookies. Browser cookies are. Instead, we can use a cloud key/value store such as Redis and leverage a plugin called Flask-Session.

Flask-Session is a Flask plugin that enables the simple integration of a server-side cache leveraging methods such as Redis, Memcached, MongoDB, relational databases, etc. Of these choices, Redis is an exceptionally appealing option.

Redis is a "NoSQL" data store that stores data in memory instead of writing data to disk (think SQL). This lack of I/O overheard users as they blaze mindlessly through your site. Redis was designed for this very purpose, is extremely quick, and is free to use when spinning up a free instance on redis.com (or whichever cloud provider you prefer).

Becoming Familiar with Flask-Session

To understand Flask-Session's offering, a good place to start is by peaking at the settings that Flask-Session accepts:

Session Type Description
SESSION_TYPE

Specifies which type of session interface to use. Built-in session types:

  • null: NullSessionInterface (default)
  • redis: RedisSessionInterface
  • memcached: MemcachedSessionInterface
  • filesystem: FileSystemSessionInterface
  • mongodb: MongoDBSessionInterface
  • sqlalchemy: SqlAlchemySessionInterface
SESSION_PERMANENT Whether to use a permanent session (default: True)
SESSION_USE_SIGNER Whether to sign the session cookie sid or not, if set to True, you have to set flask.Flask.secret_key. (default: False)
SESSION_KEY_PREFIX A prefix that is added before all session keys. This makes it possible to use the same backend storage server for different apps. (default: “session:”)
SESSION_REDIS An open Redis session to store session data in.
SESSION_MEMCACHED A memcache.Client instance, default connect to 127.0.0.1:11211
SESSION_FILE_DIR The directory where session files are stored. Default to use flask_session directory under current working directory.
SESSION_FILE_MODE The file mode wanted for the session files, default 0600
SESSION_MONGODB A pymongo.MongoClient instance, default connect to 127.0.0.1:27017
SESSION_MONGODB_DB The MongoDB database you want to use, default “flask_session”
SESSION_MONGODB_COLLECT The MongoDB collection you want to use, default “sessions”
SESSION_SQLALCHEMY A flask.ext.sqlalchemy.SQLAlchemy instance whose database connection URI is configured using the SQLALCHEMY_DATABASE_URI parameter
SESSION_SQLALCHEMY_TABLE The name of the SQL table you want to use, default “sessions”

Our options for storing session variables via an in-memory store are essentially limited to either SESSION_MEMCACHED or SESSION_REDIS. Redis has quickly become a cornerstone of modern infrastructure for many reasons not worth describing in detail here, so SESSION_REDIS it is.

Getting Started

To get started, we need to install two libraries related to sessions in Flask: Flask-Session and Redis. We'll need to install the core flask library, as well as a few Flask plugins we've become familiar with:

$ pip3 install flask flask-session redis flask-sqlalchemy \
    flask-wtf flask-assets pymysql email_validator python-dotenv

Install dependencies

Configuration

Next, we need to configure our app. In our config.py file, we need to import the Redis library with import redis (we'll get to that in a minute). Next, we need to set the following variables in config.py:

  • SECRET_KEY: Flask-Session won't work without a secret key; it's important to set this to a random string of characters (as always, make sure this is secure).
  • SESSION_TYPE: Will be set to SESSION_TYPE=redis for our purposes.
  • SESSION_REDIS: The URI of our cloud-hosted Redis instance. Redis URIs are structured a bit uniquely: redis://:[password]@[host_url]:[port].

The full configuration of a Redis instance using a URI looks like this:

SESSION_REDIS = redis.from_url(environ.get('SESSION_REDIS'))

Configuring Redis sessions for Flask

If you're having trouble with your config, feel free to borrow mine (this pulls values from .env):

"""App configuration."""
from os import environ, path
import subprocess

import redis
from dotenv import load_dotenv

BASE_DIR = path.abspath(path.dirname(__file__))
load_dotenv(path.join(BASE_DIR, ".env"))


class Config:
    """Set Flask configuration variables from .env file."""

    # General Config
    ENVIRONMENT = environ.get("ENVIRONMENT")

    # Flask Config
    FLASK_APP = "main.py"
    FLASK_DEBUG = environ.get("FLASK_DEBUG")
    SECRET_KEY = environ.get("SECRET_KEY")

    # Flask-Session
    REDIS_URI = environ.get("REDIS_URI")
    SESSION_TYPE = "redis"
    SESSION_REDIS = redis.from_url(REDIS_URI)

    # Flask-SQLAlchemy
    SQLALCHEMY_DATABASE_URI = environ.get("SQLALCHEMY_DATABASE_URI")
    SQLALCHEMY_TRACK_MODIFICATIONS = False
    SQLALCHEMY_ECHO = False

    # Flask-Assets (Optional)
    LESS_BIN = subprocess.run("which lessc", shell=True, check=True)
    ASSETS_DEBUG = False
    LESS_RUN_IN_DEBUG = False
    STATIC_FOLDER = "static"
    TEMPLATES_FOLDER = "templates"
    COMPRESSOR_DEBUG = environ.get("COMPRESSOR_DEBUG")

config.py

Initializing a Flask-Session Application

We know much about the Flask application factory and how to initialize other Flask plugins, such as Flask-SQLAlchemy and Flask-Login. Flask-Session is initialized in the same way. We set a global variable first and then initialize the plugin with session.init_app(app). This is an example of an __init__.py file initializing Flask-Session, Flask-SQLAlchemy, and Flask-Login (This builds on the source code we used to implement Flask-Login):

"""Initialize app."""
from flask import Flask
from flask_login import LoginManager
from flask_session import Session
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()
login_manager = LoginManager()
session = Session()


def create_app():
    """Construct the core flask_session_tutorial."""
    app = Flask(__name__, instance_relative_config=False)
    app.config.from_object("config.Config")

    # Initialize Plugins
    db.init_app(app)
    login_manager.init_app(app)
    session.init_app(app)

    with app.app_context():
        from . import routes
        from . import auth
        from .assets import compile_static_assets

        app.register_blueprint(routes.main_blueprint)
        app.register_blueprint(auth.auth_blueprint)

        # Create static asset bundles
        compile_static_assets(app)

        # Create Database Models
        db.create_all()

        return app

__init__.py

With Flask-Session initialized as session, we're ready to see how this works!

Let's Mess With Some Variables

The cool thing about Flask-Session is that it extends Flask native session object. Once we've configured our Flask app to use Flask-Session (as we already have), we can work with Flask session variables like we would have if we were still using cookies. To get started, we import session from flask via from flask import session.

Let's review the basics of managing values in a Flask session.

Setting a Value

Setting a variable on a session looks a lot like setting values for any old Python dictionary object:

session['key'] = 'value'

Set a value for the current session

Retrieving a Value

With a value saved to our session, we can retrieve and reuse it with .get():

session_var_value = session.get('key')

Retrieve a value from the current session by name

Removing a Value

If the value we've saved has been used and is no longer needed, we can remove variables from our session using .pop():

session.pop('key', None)

Retrieve and remove a session value

Demonstrating Sessions

To see sessions working in action, we'll create a couple of routes that create and display values saved to a session. First, we'll set a session variable in the route for our application's homepage. Next, we'll create a route for the specific purpose of displaying this variable:

"""Routes for logged-in users."""
from flask import (
    Blueprint,
    Response,
    redirect,
    render_template,
    session,
    url_for
)
from flask_login import (
    current_user,
    login_required,
    logout_user
)


# Blueprint Configuration
main_blueprint = Blueprint(
    "main",
    __name__,
    template_folder="templates",
    static_folder="static"
)


@main_blueprint.route("/", methods=["GET"])
@login_required
def dashboard() -> Response:
    """
    Logged in Dashboard screen.
    
    :returns: Response
    """
    session["redis_test"] = "This is a session variable."
    return render_template(
        "dashboard.jinja2",
        title="Flask-Session Tutorial",
        template="dashboard-template",
        current_user=current_user,
        body="You are now logged in!",
    )


@main_blueprint.route("/session", methods=["GET"])
@login_required
def session_view() -> Response:
    """
    Display session variable value.
    
    :returns: Response
    """
    return render_template(
        "session.jinja2",
        title="Flask-Session Tutorial",
        template="dashboard-template",
        session_variable=str(session["redis_test"]),
    )


@main_blueprint.route("/logout")
@login_required
def logout() -> Response:
    """
    User log-out logic.
    
    :returns: Response
    """
    logout_user()
    return redirect(url_for("auth.login"))

routes.py

At first glance, it doesn't seem like anything has happened on our app's home page:

The logged-in homepage of our Flask app
The logged-in homepage of our Flask app

Behind the scenes, we've set a session variable named redis_test. Navigate to https://127.0.0.1:5000/session, and you'll be greeted with the following:

Displaying a Flask session variable's value
Displaying a Flask session variable's value

Not only did we create a value that can persist across views, but since we've stored this value in a cloud Redis instance, it should also persist across devices. I connected to my Redis instance using a GUI, and this is what came back:

Redis database table with stored records.
A view of what's in our Redis instance

Ahh, the keys and the values we stored are encrypted! This is why it was so important to set our secret key earlier. Nevertheless, we can see our session variables are successfully decoupled. Instead of depending on the user's browser or our app's local server, user session variables are nice and comfy in the cloud.

Get Your Hands Dirty

The best way of learning is by doing, of course. For anybody interested, I've uploaded the source code for this tutorial to Github here:

hackersandslackers/flask-session-tutorial
:floppy_disk: :bow: Example Flask project for implementing Flask-Session with Redis. - hackersandslackers/flask-session-tutorial

This has been another episode of Building Flask Apps! Join us next time when we... well, I'm not sure yet. Join us anyway! Peace fam.