Simple username & password protection of a bokeh server - python

I have a simple bokeh server application and I want to expose it on a Linux-based Azure node. The server is there up and running.
My question is: how to protect the content by username and password? I do not need necessarily authentication of users.
My ideas so far (not tried, may not work)
To create an extra bokeh server page with a text field.
On the callback for a button, to add the test if the password fits. If it does, to redirect to the original server page. Otherwise, inform the user about wrong credentials.

You can try to disable generation of session id's by bokeh server and generate them by external application only after user authentication:
(Based on this part of bokeh documentation)
Generate secret key with bokeh secret command:
$ bokeh secret
oIWDL7DVYCaBJG9eYQ2Wvf2f2uhOAIM8xNS8Kds3eizV
Set BOKEH_SECRET_KEY environment variable to generated value;
$ export BOKEH_SECRET_KEY=oIWDL7DVYCaBJG9eYQ2Wvf2f2uhOAIM8xNS8Kds3eizV
Set another environment variable:
$ export BOKEH_SIGN_SESSIONS=True
Run bokeh server with --session-ids external-signed argument:
$ bokeh serve myApp --session-ids external-signed
In this mode user should provide valid (signed) session id to access bokeh server.
Run simple external process to ask users for login and password and generate id's for them.
Here is the example based on snippet from Flask documentation:
from functools import wraps
from flask import request, Response, redirect, Flask
from bokeh.util import session_id
app = Flask(__name__)
def check_auth(username, password):
return username == 'valid_user' and password == 'valid_password'
def authenticate():
"""Sends a 401 response that enables 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"'})
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
#app.route('/')
#requires_auth
def redirect_to_bokeh():
s_id = session_id.generate_session_id()
return redirect("http://<bokeh-server-addr>:<port>/?bokeh-session-id={}".format(s_id), code=302)
if __name__ == "__main__":
app.run()
Now to access bokeh server user should go to Flask application and specify login and password.

Related

Is form/login still required when using FlaskLDAP3Login for Single Sign-On purpose?

Simply saying, I am developing an app using Flask. For this app I am trying to implement a Single Sign-On, so a user never needs to enter credentials, e.g. username and password.
The authentication and authorization in this case will go through Kerberos together with LDAPS. The Kerberos part is not done yet, however, Kerberos is intended to get a "username" via a middleware currently logged into a system (Windows), when requesting an app's link. Afterwards this variable i.e. "username" will be proceeded with LDAPS, to check whether a user belongs to Active Directory or not. If yes - provide access and permission to a web site, if no - forbid.
However, since my user wont type anything, I do not understand whether I need to use either the Flask Form (flask-wtf) or the Flask Login (flask-login e.g. UserMixin) as well as how shall I provide an access to my user?
I was able to set up the FlaskLDAP3Login in 'config.py' and than run the '__init__.py'
from flask import Flask
from config import Config
from flask_login import LoginManager
from flask_ldap3_login import LDAP3LoginManager
app = Flask(__name__)
app.config.from_object(Config)
login_manager = LoginManager(app) # Setup a Flask-Login Manager
ldap_manager = LDAP3LoginManager(app) # Setup a LDAP3 Login Manager
from app import routes
than I got the following exception:
Exception: Missing user_loader or request_loader. Refer to
http://flask-login.readthedocs.io/#how-it-works for more info.
than I found this answer, but using this decorator #login_manager.user_loader is probably not enough, is not it?
My assumption is to create a decorator, similar to this one that will allow/forbid an access to a user:
import getpass
from flask import Flask, request, make_response
from functools import wraps
def auth_required(f):
#wraps(f)
def decorated(*args, **kwargs):
current_user = getpass.getuser() # current_user will be later acquired via Kerberos
auth = ldap_manager.get_user_info_for_username(current_user)
if auth:
return f(*args, **kwargs)
return make_response('Could not verify your login!', 401, {'WWW-Authenticate': 'Basic realm="You are not our user!"'})
return decorated
Also, I cannot find a similar or even related thread e.g.
github/flask-ldap3-login/flask_ldap3_login/forms.py
Authenticate with Flask-LDAP3-Login based on group membership
Flask Authentication With LDAP
Integrate LDAP Authentication with Flask
The Flask Mega-Tutorial Part V: User Logins

Handling login Flask web application

I'm currently building my very first own project, a web application with Flask which interacts with the Spotify API. The project is as good as ready after extensive testing locally and on a Heroku staging environment. However I'm currently experiencing a small login 'bug' I can't seem to wrap my head around.
When clicking a Login button, the application sends a request to the Spotify API, which sends back an authorization code upon confirmation of the user to read his data. This confirmation will result in the user being redirected to the '/profile route' of the web app. The flow of the login process so far on different environments:
Locally: process has always ran smoothly (read: click the Login button once which redirects the user to /profile route)
Staging (Heroku): clicking the login button generates the correct request to the Spotify API. However, when clicking it for the for the 1st time I get redirected to the login page (due to my login_required Flask decorator). When clicking it for the 2nd time it also generates the correct request and correctly sends the user to the '/profile route'.
I can see in the server logs clicking the login button for the 1st time generates the correct request. But it seems as if the validation by the login_required decorator of the '/profile route' goes 'too fast'? Or does this have something to do with caching? Because I'm also able to reproduce the bug (sometimes) by removing cache and hard refreshing the page.
I recently added a SECRET_KEY to session and changed the SESSION_PERMANENT from False to True but I don't think this is causing the issue? Some of the code I think might be causing this little bug:
# Ensure responses aren't cached
#app.after_request
def after_request(response):
response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
response.headers["Expires"] = 0
response.headers["Pragma"] = "no-cache"
return response
# Configure session to use filesystem
app.config['SECRET_KEY'] = os.urandom(64)
app.config["SESSION_FILE_DIR"] = mkdtemp()
app.config["SESSION_PERMANENT"] = True
app.config["SESSION_TYPE"] = "filesystem"
Session(app)
def login_required(f):
"""
Decorate routes to require login:
http://flask.pocoo.org/docs/1.0/patterns/viewdecorators/
"""
#wraps(f)
def decorated_function(*args, **kwargs):
if session.get("authorization_header") is None:
return redirect(url_for('login'))
return f(*args, **kwargs)
return decorated_function
#app.route("/")
def index():
""" Shows landing page """
if session.get("authorization_header") is not None:
return redirect(url_for('profile'))
return render_template("index.html")
#app.route("/login")
def login():
""" Shows login page """
if session.get("authorization_header") is not None:
return redirect(url_for('profile'))
return render_template("login.html")
#app.route("/logout")
#helpers.login_required
def logout():
""" Logs user out """
# Forget any user_id
session.clear()
# Redirect user to login form
return redirect(url_for("index"))
#app.route("/profile")
#helpers.login_required
def profile():
""" Shows overview of user's Spotfy profile """
Not sure if this is your issue, but could cause some weird behaviour...
app.config['SECRET_KEY'] = os.urandom(64) This will result in a different secret key being set on each heroku dyno (if running multiple workers; the default is 2 for syncronous workers) as that random string is generated dynamically at worker boot time.
As a side note, os.urandom isn't for generating keys. Instead use the secrets module, and set the resulting string as a config var in heroku CLI and load it in your app with:
import os
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY')

Using JWT between two Flask applications

I have two separate Flask applications, one is an API with the domain "admin.company.com" and the second one is a dashboard under the domain "dashboard.company.com".
My main goal is to secure the api and enable authentication.
I set up authentication on the api and when I'm testing it via the swagger-ui it works good with no issues. I manage to authenticate myself and submit requests. On top of that, in the token_required() below I coded a section that expects to receive JWT and decode it:
def token_required(f):
#wraps(f)
def decorator(*args, **kwargs):
token = None
if 'jwt-token' in request.headers:
token = request.headers['jwt-token']
if not token:
return jsonify({'message': 'a valid token is missing'})
try:
current_user = False
# for authentication via the swagger-ui
if token == 'my_awesome_password':
current_user = 'admin'
else:
data = jwt.decode(token, app.secret_key)
current_user = 'admin' if data['public_id'] == 'admin' else False
if not current_user:
return jsonify({'message': 'token is invalid'})
except:
return jsonify({'message': 'token is invalid'})
return f(*args, **kwargs)
return decorator
The problem is with the dashboard application:
On the dashboard app, I configured the /login route to generate JWT (which needs to be sent to the api app in every HTTP request I made), then do a set_cookie() with httpOnly=True flag (for security purposes), but then how can the JavaScript access it when it has to make XHR requests? Having the httpOnly flag on makes it unreachable.
If using httpOnly=True is not the ideal way in this case, how can I tackle it and make sure the JWT will always be sent to the api app in the request headers?
I'm using PyJWT package.
Thanks for any help.
If you use JWTs for XHR requests, then you don't necessarily need to use cookies – the browser/consumer could store the token and attach it to every request. If you do want to use cookies (this is also a viable option) then you don't need to worry about attaching the JWT as the browser will do that automatically for the domains you specified in the set_cookie command.

How to login user through all routes with flask?

I'm stuck in a loop, I have a web app, the login and signup was made using Flask on the Backend and Jinja for templating, no JS was used for these two routes, I used WTForms for form validation and Flask Login to handle login .
Now, when user login he is redirected to a dashboard, the dashboard is a single page application that use React, I send HTTP request to my API which is built in Flask (same app, but different blueprints).
The problem is that these APIs routes are avalable only for authenticated users, so I used a token system to authenticate API calls from the client to the server .
To do so, I created a request loader for my login manager, that using a token parameter in the query would decode it , get the email and password and return the respective User, otherwise if it fail it returns None
#login_manager.request_loader
def load_user(request):
if "token" in request.args:
decoded_token = base64.standard_b64decode(request.args["token"])
email = decoded_token.split(b':',1)[0]
password = decoded_token.split(b':',1)[1]
possible_user = User.select().where(User.email == email)[0]
if possible_user.password.encode() == password:
return possible_user
else:
return None
else:
return None
return None
This token is sent to the user when he login and get redirected to the dashboard :
User login successfULY
Get redirected to dashboard
Dashboard make an ajax call to "/user-data" , which is protected, this route should use current_user to encode the token and send it back to dashboard single page app to use it in further API calls .
The problem:
When I request the "/user-data" through AJAX from dashboard, current_user is empty thus, the call return a 401 unauthorized request even though the user did login, and in the login route, when I print current_user I get the current user logged in as expected . So my question is how can I keep some way to exhcange credentials between login and "user-data" route ? I tried storing the data in a session in the login route then re-use it in the '/user-data' but the session becomes empty whenever 'user-data' is called .
Here's 'user-data' route :
#auth_bp.route("/user-data")
#login_required
def user_data():
# Return Base 64 encode username:password to use in API calls!
print("Current user")
print(current_user)
print(session)
code = base64.standard_b64encode(current_user.email.encode() + b':' + current_user.password.encode())
print(base64.standard_b64decode(code))
return code

Flask/Python decoding username NTLM or Negotiate Authentication Header

I have my Flask app hosted in IIS in our intranet. In Flask, I'm able to get the www-authenticate header, but I need to determine the windows username. I did have Basic Authentication enabled and was able to parse out the username via that method, but I want this to be transparent to the user. In IE I have the option set to auto login to intranet sites so they're not prompted for a username and password.
I am able to get a string that can either begin with NTLM or Negotiate (depending on the setting in IIS) and a long auth string. What is a reliable way I can decode this in python/Flask?
Got it.
class RemoteUserMiddleware(object):
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
user = environ.pop('HTTP_X_PROXY_REMOTE_USER', None)
environ['REMOTE_USER'] = user
return self.app(environ, start_response)
app.wsgi_app = RemoteUserMiddleware(app.wsgi_app)
Then in the view by doing this:
username = str(request.environ.get('LOGON_USER'))

Categories

Resources