Hi everyone,
I’m still going in circles wit authentication inside my dash application.Although I can make both dash-okta-auth and dash-google-auth work out of the box (after proper configuration of the authentication provider endpoints) I cannot get user information (id of whoever is logged on).
Although I am not a proper developer, I went as far as making a local copy of dash-okta-auth (a close derivative of dash-google-auth) in my project to do some minor debugging – interestingly enough, inside the class OktaOAuth(Auth), the code
works just fine, but another request that would, in principle, provide the information I need would proably require almost mimicking what dash-okta-auth is doing.
So, I’m almost stuck on this matter, but I need to find a solution for this urgently.
Any help out there? Someone that has a profound knowledge on authentication?
Thanks.
Personally I register my Dash app on another main Flask app and protect the routes to the Dash app. That way I can use any Flask Auth solution available, without worrying about actual Dash compatibility.
I installed the newest dash-okta-oauth2 and it works fine, but I still don’t know where to extract the user info from – I’ve written an email to the package developer, but no answer so far. Meanwhile, I also followed your advise and started looking for a solution using flask-dance, since my app already is a Dash app inside a Flask server. I tried the following simplified code, adapted from the flask-dance repository, which runs, but the unfortunately the google login window is never called (using incognito). I also read the blogs you recommended about dash-inside-flask solutions, but they seem quite convoluted.
import os
from flask import Flask, redirect, url_for
from flask_dance.contrib.google import make_google_blueprint, google
import dash
import dash_html_components as html
server = Flask(__name__)
server.config.update({
'GOOGLE_OAUTH_CLIENT_ID': 'XXXXXXXXXXXXXXXXX',
'GOOGLE_OAUTH_CLIENT_SECRET': 'XXXXXXXXXXXXXXXXXX'
})
server.secret_key = os.environ.get('FLASK_SECRET_KEY', 'whatever you want it to be 12345')
os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'
external_stylesheets = []
app = dash.Dash(__name__, server=server, url_base_pathname='/', external_stylesheets=external_stylesheets,
meta_tags=[{'name': 'viewport', 'content': 'width=device-width, initial-scale=1'}])
app.config['suppress_callback_exceptions']=True
google_bp = make_google_blueprint(scope=["profile", "email", "openid"])
server.register_blueprint(google_bp, url_prefix="/login")
app.layout = html.Div(
children=[
html.P('Hello world')
],
)
@server.route("/")
def index():
print('this message never shows up ...')
if not google.authorized:
return redirect(url_for("google.login"))
resp = google.get("/oauth2/v1/userinfo")
assert resp.ok, resp.text
return "You are {email} on Google".format(email=resp.json()["email"])
if __name__ == '__main__':
app.run_server(debug=True)
Do I need to construct a proper Flask folder structure for the declared routes before running this code? What am I missing? My idea of using Dash is not having to deal with Flask too much.
I feel like I’m going into a rabbit’s hole by exploring these flask and flask-dance solutions, when all I want is the user info from the OktaAuth or GoogleAuth objects.
Thanks.
yes, a proper folder structure would be required I think yes. I didn’t try to combine flask-dance with dash yet, so I can only do guess work here. You can strip down the example in that blog post to your needs.
This would be the relevant flask part. Exchange the permissions.admin(dashapp.server.view_functions[view_func]). This must be adapted to the flask-dance solution, ideally by just exchanging the permissions function here. Check the blog post again, once you have set that up, you can forget about flask and continue working in dash. I’m not too experienced tho, there might be better solutions, but this is the way I would go now.
Hope it helps.
First file
# your_app_folder/dashapp.py
from app.app import create_app
server = create_app()
Second file
# your_app_folder/app/app.py
import dash
from flask import Flask
from app.infrastructure import permissions
def create_app():
# Create Flask server app
server = Flask(__name__)
register_dashapps(server)
setup_db(server) #your database storage solution
return server
########### REGISTER DASHAPPS ###########
def register_dashapps(app):
'''abc is my dashapp folder, I chose to build a multipage app
with a sidebar from this example: https://dash-bootstrap-components.opensource.faculty.ai/examples/simple-sidebar/'''
from app.abc.sidebar import layout
from app.abc.callbacks import register_callbacks
# Meta tags for viewport responsiveness
meta_viewport = {"name": "viewport", "content": "width=device-width, initial-scale=1, shrink-to-fit=no"}
# Create dash app passing our server
abc = dash.Dash(__name__,
server=app,
url_base_pathname='/dashboard/',
assets_folder=get_root_path(__name__) + '/abc/assets/',
meta_tags=[meta_viewport],
)
abc.config.suppress_callback_exceptions = True
# Push an application context so we can use Flask's 'current_app'
with app.app_context():
abc.title = 'abc'
abc.layout = layout
register_callbacks(abc)
_protect_dashviews(abc) # this is the important function
def _protect_dashviews(dashapp):
''' permissions.admin is the relevant part for you, this is my own permissions function,
but in the example blog post, this is done with a flask-login solution and an additional register_extensions(server) function and you could
probably exchange the flask-login part in that with flask-dance here'''
# Method to protect dash views/routes
for view_func in dashapp.server.view_functions:
if view_func.startswith(dashapp.config.url_base_pathname):
dashapp.server.view_functions[view_func] = permissions.admin(dashapp.server.view_functions[view_func])
Thank you @nicohein for your fast reply.
Now I see, the problem with the old dash-okta-auth is that it doesn’t register the atributes from the /userinfo call in the SecureCookieSession object. I ran your solution with the old package and that info is nowhere to be found in variable session.
But, now, contrary to what I thought, I cannot make your package work, because there are differences in the URIs/redirects between your package and the old one. My app URIs at okta are registered as
https://xxxxxxxx/login/okta/authorized
The old package sends the redirect to the login page at
Yours seems to be the correct one but – for some reason I cannot understand – the old one is working and the new one throws a 404 (page not found) error.
I have been going back and forth with the new one and the old one, and it is always the same – one works, the other one does not.
Should I register another URI at Okta that works with your package? Any advice?
Thanks.
sorry for the delay in my responses. Please try to set the OKTA_BASE_URL like with /oauth2/default/v1 endpoint. E.g. OKTA_BASE_URL=https://dev-xxxxxxx.okta.com/oauth2/default/v1.
You should also be able to find the correct URL for your Developer account on the Okta admin page under API > Authorization Server.
Hope this solves the issue. Otherwise please try to run app.py example after cloning the https://github.com/nicohein/dash-okta-oauth2 repository and provide the logs generated.
So, for all of you out there still strugling to make Okta authentication work with added userinfo, here goes my code that puts everything together, thanks to @nicohein and dash-okta-oauth2 :
from flask import Flask, session
import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output, State
from dash_okta_oauth2 import OktaOAuth
server = Flask(__name__)
server.config.update({
'OKTA_OAUTH_CLIENT_ID': xxxxxxxxxxxxxxxxxxxxxxx',
'OKTA_OAUTH_CLIENT_SECRET': 'xxxxxxxxxxxxxxxxxxxxxxxxx',
'OKTA_BASE_URL': 'https://dev-xxxxxx.okta.com/oauth2/default/v1',
})
server.secret_key = os.environ.get('FLASK_SECRET_KEY', 'xxxxxxxxxxxxxxxxxxxxxxxxxxx')
# clean this for production
os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'
external_stylesheets = []
app = dash.Dash(__name__, server=server, url_base_pathname='/', external_stylesheets=external_stylesheets,
meta_tags=[{'name': 'viewport', 'content': 'width=device-width, initial-scale=1'}])
app.config['suppress_callback_exceptions']=True
app.title = xxxxxxxxxxxxx'
auth = OktaOAuth(app)
@server.route('/')
def MyDashApp():
return app.index()
@app.callback(
[Output('xxxxxxx', 'children')],
[Input('yyyyyyy', 'children')],
)
def your_callback (ch):
# get the fields of interest from session
profile = session['user_profile']
# use them for some purpose
return []