I'm trying to implement a really simple webpage for internal monitoring. It is supposed to display some data, which is updated in real-time via socketio. The server runs a thread in the background, which fetches the data and relays it to the client.
I'd like to protect the page with a login form. To keep things simple, I chose HTTP Basic Auth, mostly because I didn't want to design a login form.
I have done the following:
- Under
@login_manager.request_handler, I check forrequest.authorization. If it is valid, I return an authenticatedUserobject. - Under
@login_manager.unauthorized_handler, I trigger the authentication dialog. - The
'/'page is protected with@login_required. - I also intercept the
socketio.on('connect')event and I check forcurrent_userthere. If it is not authenticated, I drop the connection.
Here's the entire working example:
## Standard imports, disregard them
import functools
import gevent
## Otherwise I'm getting KeyError on shutdown
import gevent.monkey
gevent.monkey.patch_all()
from flask import Flask, request, Response
from flask.ext.login import LoginManager, UserMixin, login_required, current_user
from flask.ext.socketio import SocketIO
## To see the logging.debug call in socketio.on('connect')
import logging
logging.getLogger().setLevel(logging.DEBUG)
## App configuration
app = Flask(__name__)
app.debug = True
app.config['SECRET_KEY'] = 'a long and random string'
login_manager = LoginManager()
login_manager.init_app(app)
socketio = SocketIO(app)
## This thing sends updates to the client
class BackgroundThread(gevent.Greenlet):
def run(self):
while True:
socketio.emit(
'my event',
{'my field': 'my data'},
namespace='/my-namespace'
)
gevent.sleep(2)
## Not bothering with a database
class User(UserMixin):
users = {
u'1': (u'myname', u'mypass')
}
def __init__(self, username, password):
self.username = username
self.password = password
def get_id(self):
return u'1'
@classmethod
def get_by_username(cls, requested_username):
for username, password in cls.users.itervalues():
if username == requested_username:
return User(username, password)
return None
## From https://flask-socketio.readthedocs.org/en/latest/
def authenticated_only(f):
@functools.wraps(f)
def wrapped(*args, **kwargs):
if not current_user.is_authenticated():
request.namespace.disconnect()
else:
return f(*args, **kwargs)
return wrapped
## The password is checked here
@login_manager.request_loader
def load_request(request):
auth = request.authorization
if auth is not None:
username, password = auth['username'], auth['password']
user = User.get_by_username(username)
if user is not None and user.password == password:
return user
return None
## From http://flask.pocoo.org/snippets/8/
@login_manager.unauthorized_handler
def http_basic_auth():
return Response(
'Could not verify your access level for that URL.\n'
'You have to login with proper credentials', 401,
{'WWW-Authenticate': 'Basic realm="Login Required"'})
@app.route('/')
@login_required
def index():
return "My page" # in real code this is actually a render_template call
@socketio.on('connect', namespace='/my-namespace')
@authenticated_only
def test_connect():
logging.debug('Client connected: {.username}.'.format(current_user))
if __name__ == '__main__':
thread = BackgroundThread()
thread.start()
socketio.run(app)
- Is this setup secure, provided that I use HTTPS with a self-signed certificate?
- The
Flask-Logindocs stress that to actually login the user, I have to explicitly calllogin_user. I don't do that and yet I can log in. How is that possible?
UPD: In the foreseeable future I am going to be the only user, so mostly I am concerned whether it is possible to intercept and decrypt the traffic, or send data through the Websocket connection without being authenticated.