2017-07-03T15:45:28Z

Flask-SocketIO and the User Session

The way user sessions are handled in my Flask-SocketIO extension has always been a pain point for me. I tried to make sessions work almost the same as they work on regular Flask routes, but the "almost" part is what makes it confusing for most people.

In this short article and its companion video, I will try to explain why this is not trivial, and also will go over some improvements I just released that I hope will improve the use cases on which users seem to always trip.

Default User Session Handling

The way user sessions are handled by default is by forking the Flask user session at the time the client connects to the server over Socket.IO. What does it mean to "fork" the session? It means that the contents of the Flask user session are copied over to a brand new session, specifically created for the Socket.IO connection. This session is different than the Flask session, it is actually handled by the Flask-SocketIO extension.

In practice, this handling of user sessions means that Socket.IO event handlers are going to see anything that was in the Flask user session at the time of connection. But any changes that are made to the Flask session through HTTP routes after the Socket.IO connection took place will not be accessible from Socket.IO handlers. Likewise, any changes made to the session from Socket.IO handlers will not be accessible through regular Flask routes. There are basically two user sessions, one for HTTP and one for Socket.IO.

To summarize:

  • The Flask session is copied to the Socket.IO session at the time of the Socket.IO connection.
  • Changes made to the session in Flask routes after the Socket.IO connection was made will not be accessible on the Socket.IO session.
  • Changes made to the session in Socket.IO event handlers will not be accessible on the Flask user session.

You may wonder why such a convoluted way to handle sessions. The reason lies in the fact that the server is unable to send cookies to the client through a WebSocket connection. If a Socket.IO handler makes a change to the user session, a new version of the session cookie would need to be sent to the client, and there is no standard way to do that.

Flask-Socketio's New Managed Sessions Setting

Starting with the 2.9.0 release of Flask-SocketIO, there is a new setting that controls how sessions are managed by the extension, with the optional manage_session argument given to the SocketIO class. The default value is manage_session=True, which means that Flask-SocketIO will manage its own user sessions, as described in the previous section.

Passing manage_session=False disables the extension's handling of user sessions, and instead, the Flask session handling is used. This has no practical use when working with the regular Flask user sessions based on cookies, because as explained above, cookies cannot be sent to the client on a WebSocket connection, but there are a few Flask extensions that implement server-side sessions, which for most usages, bypass the problem of having to send cookies to a client connected over WebSocket.

For example, the Flask-Session extension supports sessions with server-side storage on Redis, Memcached, MongoDB, SQLAlchemy databases or regular disk files. Updating any of these sessions is possible in a Socket.IO event handler, as long as the client already has the session id, which should be true when the session is first accessed from a regular HTTP route. As an additional limitation, the session cannot be discarded, as that will cause a new session to be created, and that will change the session id.

Using Server-Side Sessions

To use server side sessions with Flask-Session, you just need to initialize the extension and decide what storage you want to use for your sessions. The easiest configuration is to use disk files:

from flask_socketio import SocketIO
from flask_session import Session

app.config['SESSION_TYPE'] = 'filesystem'
Session(app)
socketio = SocketIO(app, manage_session=False)

With the above configuration, the server will create a subdirectory called flask_session in the current directory and write user sessions for all clients in it. These files will be written to by Flask or by Flask-SocketIO whenever changes to the session are made.

If you set manage_session=True instead, the user sessions will continue to be forked as described above, regardless of what type of session you use.

There is a complete example Flask-SocketIO application that uses this type of sessions in the official repository, called sessions.py. In the video above I demonstrate how this application works using all the different session modes available in the 2.9.0 release.

Flask-Login Support

I frequently receive questions regarding how to use Flask-Login with Flask-SocketIO. The good news is that the current_user context variable is fed from the user session, so that means that you can reference current_user in your Socket.IO event handlers without any problems. And if you set manage_session=False in combination with server-side sessions, you can also use login_user() and logout_user() from Socket.IO event handlers, and the changes to the user session are going to also be seen from Flask routes.

The login_required decorator is more tricky. This decorator was designed to work with Flask routes, so it cannot be used on Socket.IO event handler functions. The Flask-SocketIO documentation includes a custom decorator that has similar functionality as Flask-Login's login_required, but is designed to work with Socket.IO event handlers. For your convenience, here is the decorator source code:

import functools
from flask_login import current_user
from flask_socketio import disconnect

def authenticated_only(f):
    @functools.wraps(f)
    def wrapped(*args, **kwargs):
        if not current_user.is_authenticated:
            disconnect()
        else:
            return f(*args, **kwargs)
    return wrapped

You can use this decorator as follows:

@socketio.on('my event')
@authenticated_only
def handle_my_custom_event(data):
    emit('my response', {'message': '{0} has joined'.format(current_user.name)},
        broadcast=True)

Conclusion

I hope the new server-side session handling in Flask-SocketIO 2.9.0 helps alleviate the problems I see users having. If you have any suggestions to make more improvements, or you have experienced problems with user sessions not addressed with these changes, definitely let me know in the comments below, or with an issue on the GitHub repository.

Leave a Comment