Dear bokeh community,
I try to protect my bokeh webapp, by allowing only externally signed session ids that are generated in a very basic flask application after user authentication.
This works, but it seems that bokeh does not check for the signed session ids. Whoever goes on the hosted bokeh website directly gets a session assigned and can circumvent the authentication. It seems the argument session_ids=‘external-signed’ has no effect.
Something similar has been discussed here:
Flask, Bokeh, externally signed sessions: Invalid token signature error - Community Support - Bokeh Discourse
However, i could not reproduce the solution. I would like to be able to start both servers (flask and bokeh) from one script.
Here is the script that uses an invalid session id and should therefore be rejected:
import threading
from functools import wraps
from flask import Flask, request, Response, redirect
from bokeh.server.server import Server
from bokeh.plotting import figure
from bokeh.util.token import generate_session_id, generate_secret_key
from bokeh.models import ColumnDataSource, Slider, Div, TextAreaInput, CustomJS, PreText
from bokeh import layouts
import os
import logging
from bokeh.layouts import column
from random import random
def vola_tracker_app(doc):
headline = Div(text="<h4>My headline</h4>")
# Create the data source
data = {'x': [], 'y': []}
source = ColumnDataSource(data)
# Create the figure
fig = figure(title='Live Streaming Bokeh Plot')
fig.line('x', 'y', source=source, line_width=2)
# Function to update the plot data
def update():
new_data = {'x': [], 'y': []}
# Generate new random data
for i in range(10):
new_data['x'].append(i)
new_data['y'].append(random())
source.data = new_data
# Initialize regular callback to update the plot
doc.add_periodic_callback(update, 2000)
layout = column(headline, fig)
doc.add_root(layout)
authentication_app = Flask(__name__)
def check_auth(username, password):
return username == correct_username and password == correct_password
def authenticate(): #Version, where browser stores during the same session => reenter after restarting browser (a new tab is not enough to trigger an additional request)
"""Sends a 401 response that disables credential caching"""
response = 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"',
'Cache-Control': 'no-store, no-cache, must-revalidate',
'Pragma': 'no-cache'})
return response
def requires_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if not auth or not check_auth(auth.username, auth.password):
return authenticate()
return f(*args, **kwargs)
return decorated
@authentication_app.route('/')
@requires_auth
def redirect_to_bokeh():
s_id = generate_session_id(secret_key='123', signed=True) #TODO: How to generate valid session ids for bokeh here? And how to force bokeh to only accepted signed ids?
print('http://'+str(bokeh_app_address)+':'+str(bokeh_app_port)+'/bokeh/?bokeh-session-id={}'.format(s_id))
return redirect('http://'+str(bokeh_app_address)+':'+str(bokeh_app_port)+'/bokeh/?bokeh-session-id={}'.format(s_id), code=302)
def run_flask_app():
authentication_app.run(host=authentication_app_host, port=authentication_app_port)
if __name__ == "__main__":
correct_username = 'a'
correct_password = 'b'
bokeh_app_address = 'localhost' # Host for bokeh app; 'localhost' or RDP address of (Linux Server)
bokeh_app_port = 8000 # Port for bokeh app
authentication_app_host = 'localhost' # Host for authentication/flask app; 'localhost' or RDP address of (Linux Server)
authentication_app_port = 8888 # Port for authentication/flask app
logging.getLogger().setLevel(logging.DEBUG)
# Start the Flask app in a separate thread
flask_thread = threading.Thread(target=run_flask_app)
flask_thread.start()
# Generate secret key with bokeh secret command
bokeh_secret_key = generate_secret_key()
os.environ['BOKEH_SECRET_KEY'] = bokeh_secret_key
os.environ['BOKEH_SIGN_SESSIONS'] = 'True'
# Start the Bokeh server in the main process
server = Server({'/bokeh': vola_tracker_app}, address=bokeh_app_address, port=bokeh_app_port, allow_websocket_origin=['*'], session_ids='external-signed')
server.start()
# Keep the server running
print('Running bokeh server on: http://' + str(bokeh_app_address) + ':' + str(bokeh_app_port) + '/bokeh')
server.io_loop.add_callback(server.show, "/")
server.io_loop.start()
I very much hope to get some help on this.