This tutorial shows you how to integrate your Plotly Dash dashboard with the IBM Cloud App ID service for authentication and authorization. It uses a simple example application with a drop-down user interface (UI) component, but you can protect any Dash UI from unauthorized access by using this example.
The Dash application relies on the AppIDAuthProvider class, defined in the auth.py file within the GitHub repo, performs most of the integration with the App ID service. (Refer to the AppIDAuthProvider class in auth.py section of the README file.)
Refer to the full README file for details such as:
How the AppIDAuthProvider class implements authentication and authorization by using App ID.
How to perform a serverless deployment of your Plotly Dash dashboard by using IBM Cloud Code Engine, a fully managed serverless platform for containerized workloads.
How to develop and test your dashboard locally by configuring App ID and setting appropriate environment variables on your machine.
Most of the description within the README file is relevant to your Dash dashboard, except for the Flask web application in app.py section. That section is not applicable because although the Dash framework uses the Flask web framework underneath, even a minimal Flask application that contains a function with its route() decorator looks very different than a minimal Dash application that contains a layout UI component with an associated callback() decorated function. This is expected because Flask is a lightweight web application framework, whereas Dash is a full dashboarding framework.
Think of this tutorial as essentially a replacement of the "Flask web application in app.py" section because a significant portion of this tutorial focuses on describing a Dash application in app.py. Otherwise, this tutorial explicitly points out if a command in the README file needs to be run differently for a Dash application. Due to, for example, an additional, required option or a different option value.
Click Fork from the menu bar to copy the repository into your GitHub account. By forking the original repository, you create your own copy so that you can make Dash-specific changes without affecting the original.
Verify the information on the Create a new fork page. This tutorial assumes that you keep the Repository name unchanged as python-appid-auth. Hence, your forked repository URL is https://github.com/<your_github_username>/python-appid-auth.
Click Create fork.
Open your command-line terminal and change directory (cd) to where you want to clone your forked repository.
Move into your cloned folder by using the change directory command (cd python-appid-auth).
By cloning your forked repository, you can now make changes to the files within it. Your Dash application will use the auth.py file as-is. But you must change the port number in the Dockerfile, add one more dependency to the requirements.txt file, create a Dash-specific subclass of AppIDAuthProvider in a new file named auth_dash.py, and completely replace the contents of the app.py file, as described in Steps 2 through 5.
Step 2. Edit the Dockerfile
Use your preferred code editor to open the Dockerfile. Change the port number in the EXPOSE instruction from 5000 to 8050 because 8050 is the default port number used by a Dash application. The updated EXPOSE instruction should be as follows: EXPOSE 8050.
Save the updated Dockerfile.
Step 3. Edit the requirements.txt file
Use your preferred code editor to open the requirements.txt file. It already contains two required dependencies, flask and requests, but you must add a dash dependency to the list so it is as follows:
flask
requests
dash
Show more
Save the updated requirements.txt file.
Step 4. Create new auth_dash.py file that contains the AppIDAuthProviderDash class
Create a new file named auth_dash.py. Use your preferred code editor to put the following source code in it, then save the file.
Following is an explanation of each section of the source code that you just added to the auth_dash.py file.
Import statements
The code starts with a few import statements:
import functools
from flask import session, redirect
from dash import html, dcc
from auth import AppIDAuthProvider
Show more
The functools is used inside the check() method to define a wrapper function. Additionally, there are a couple of flask and dash imports as well as an import of the AppIDAuthProvider class from the auth.py file. The AppIDAuthProvider class is extended by the AppIDAuthProviderDash class as described next.
AppIDAuthProviderDash class definition
After the import statements, a subclass of AppIDAuthProvider, named AppIDAuthProviderDash, is defined inside the class AppIDAuthProviderDash(AppIDAuthProvider) code block.
The AppIDAuthProviderDash class defines the /startauth route by using the idiomatic route() decorator used in a Flask application. As seen in this decorator's @self.flask.route("/startauth") syntax, the AppIDAuthProvider superclass stores the Flask instance in an instance variable named flask:
The startauth() function associated with the /startauth route starts the authentication flow by calling the start_auth() method in the superclass. Before that, it stores the value of the dash_url inside the session's ENDPOINT_CONTEXT so that the user's browser will be redirected to the route stored in the dash_url variable after the authentication flow is complete. (Refer to the after_auth() method in the AppIDAuthProvider class in auth.py section of the README file for details of how the ENDPOINT_CONTEXT stored in the session is used by the superclass.) The value stored in dash_url is described in the next step.
The AppIDAuthProviderDash class also defines the / route. (Note: Ignore this code block about the / route if you are referring to this tutorial for adding authentication and authorization to your existing Dash application that already has the / route.)
The / route uses the _is_auth_active() method defined in the superclass to check whether the access token stored in the session is still valid or not. If it is, then the application is redirected to the route stored in the dash_url variable. Otherwise, the authentication flow is triggered by redirecting the browser to the /startauth route as previously described.
Because of this definition of the / route, users don't need to additionally type a route in their browser to successfully run the application. For example, a base URL such as https://python-appid-app.sog8ik3jbo6.us-south.codeengine.appdomain.cloud works just fine, and they don't have to type in a full dashboard URL such as https://python-appid-app.sog8ik3jbo6.us-south.codeengine.appdomain.cloud/dashboard/. If the / route is not defined, your users would get a 404 Not Found error if they don't explicitly type the dashboard route in their browser.
Next, the AppIDAuthProviderDash class overrides the check() method defined in the superclass as shown in the following code block.
This method definition is very similar to the superclass method except that it returns the Dash UI components instead of strings. It performs authentication and authorization checks, and it is invoked implicitly by using the @auth.check decorator on the layout_components() callback function as described later in this tutorial.
The authentication check is performed by using the _is_auth_active() method in the AppIDAuthProvider superclass. It checks whether the App ID access token is in the session and whether the token expired. If the App ID access token is not in the session, it means that the user's authentication must be checked before showing them the dashboard UI. Also, if the App ID access token is in the session but it is expired, then the user's authentication must be rechecked before displaying the dashboard UI. In both the cases, the _is_auth_active() method returns as False and the application just displays a "Log in" link, nothing else. When the user clicks on the link, the application routes to /startauth, which starts the authorization flow.
The authorization check is performed by using the _user_has_a_role() method in the AppIDAuthProvider superclass. It uses the array of App ID roles stored in the session to check whether the user has at least one role. If the array is empty, the _user_has_a_role() method returns as False and the application displays the "Unauthorized!" string.
If both authentication and authorization checks pass, then the return func(*args, **kwargs) statement invokes the target function that has the @auth.check decorator.
Step 5. Edit the app.py file
Open the app.py file with your preferred code editor and replace all of the contents in the file with the following source code. Then save the updated file.
import dash
from dash import html, dcc
from dash.dependencies import Output, Input
from auth_dash import AppIDAuthProviderDash
DASH_URL_BASE_PATHNAME = "/dashboard/"
auth = AppIDAuthProviderDash(DASH_URL_BASE_PATHNAME)
app = dash.Dash(__name__, server = auth.flask, url_base_pathname = DASH_URL_BASE_PATHNAME)
app.layout = html.Div([
html.Div(id = "page-content"),
dcc.Interval(id = "auth-check-interval", interval = 3600 * 1000)
])
# All of your Dash UI components go in this function.# Your dashboard users are not able to view those UI components unless# they are authenticated and authorized.@app.callback(Output("page-content", "children"),
Input("auth-check-interval", "n_intervals"))@auth.checkdeflayout_components(n):
# For example, the following function returns Dropdown and Div UI components that display information about# the Flask and Dash frameworks.return [dcc.Dropdown(id='frameworks_dropdown',
options=[{'label': framework, 'value': framework}
for framework in ['Dash', 'Flask']]),
html.Div(id='framework_details')]
# All callback functions for your UI components go here.# For example, following is the callback for UI components in the previous function.@app.callback(Output('framework_details', 'children'),
Input('frameworks_dropdown', 'value'))defdisplay_framework_details(framework):
details = ""if framework isNone:
details = "You have not selected a framework yet"else:
if framework == "Dash":
details = "Dash is an open source framework for developing full-blown data applications using modern UI components. It is based upon Flask, Plotly.js and React.js. This tutorial describes how you can make your Dash applications 'Enterprise Ready' by using the IBM Cloud App ID service for authentication and authorization, and the IBM Cloud Code Engine service for deployment and scaling."else:
details = "Flask is a lightweight web application framework. Although it is called a 'micro framework', it is simple but extensible. It can be used to build complex dashboards like the Dash open source framework which is based upon Flask."return details
if __name__ == "__main__":
app.run_server(host = "0.0.0.0")
Show more
Following is an explanation of each section of the source code that you just added to the app.py file.
Import statements
The code starts with a few import statements:
import dash
from dash import html, dcc
from dash.dependencies import Output, Input
from auth_dash import AppIDAuthProviderDash
Show more
In addition to the dash imports, the code requires AppIDAuthProviderDash class from the auth_dash.py file that I described in the previous step.
Variables and constant
Then the constant named DASH_URL_BASE_PATHNAME is assigned the "/dashboard/" string value, and instances of AppIDAuthProviderDash and Dash classes are created as follows:
DASH_URL_BASE_PATHNAME is passed as an argument during the instantiation of the auth object so that the application is redirected to the /dashboard/ route after the authorization flow.
DASH_URL_BASE_PATHNAME is set as the value for the url_base_pathname parameter of the app object so that the Dash framework renders the dashboard at the /dashboard/ route. (Refer to the Dash documentation for details.)
Dashboard layout and callback functions
Next, app.layout and its corresponding @app.callback are defined:
app.layout = html.Div([
html.Div(id = "page-content"),
dcc.Interval(id = "auth-check-interval", interval = 3600 * 1000)
])
# All of your Dash UI components go in this function.# Your dashboard users are not able to view those UI components unless# they are authenticated and authorized.@app.callback(Output("page-content", "children"),
Input("auth-check-interval", "n_intervals"))@auth.checkdeflayout_components(n):
...
...
Show more
The first statement in the definition of app.layout merely adds a placeholder for the layout: a html.Div() component with an ID of page-content.
The second statement in the layout definition uses a dcc.Interval component to periodically invoke the callback function layout_components() that supplies the actual layout. The interval property of the dcc.Interval component specifies the periodicity in microseconds. It is set to one hour because that is the default validity of the App ID service access token. (Refer to the App ID token lifetime documentation for details.)
Note: If you change the lifetime of your App ID access token to some other duration, then change the value of the interval parameter accordingly.
The callback function that supplies the actual layout is layout_components(). Notice how it defines the component with the auth-check-interval ID as its input. As a result, the dcc.Interval component in the app.layout definition invokes this function periodically every hour.
Also notice that the component with the page-content ID is defined as the callback's output. Therefore, the callback's return value is nothing but the layout that is added to the html.Div component in the app.layout definition.
In effect, this mechanism designates the layout_components() callback function as the controller of which UI is displayed in the dashboard. The controller is invoked every hour and its @auth.check decorator implicitly calls the check() method in the AppIDAuthProviderDash class to check whether authentication and authorization is still valid. If it isn't, then either the "Log in" link or the "Unauthorized!" text returned by the check() method is displayed as previously described. If it is, then the UI components returned by the layout_components() function are displayed as described next.
The example Dash application in this tutorial contains a simple drop-down UI component that displays the Flask and Dash framework names. When a user selects a framework name, a description of the framework appears after the drop-down UI component. The layout_components() function returns the UI components as shown in the following code snippet:
deflayout_components(n):
# For example, this function returns Dropdown and Div UI components that display information about# the Flask and Dash frameworks.return [dcc.Dropdown(id='frameworks_dropdown',
options=[{'label': framework, 'value': framework}
for framework in ['Dash', 'Flask']]),
html.Div(id='framework_details')]
Show more
Following is the callback function for the UI. Its input is frameworks_dropdown, which is the ID of the drop-down UI component that I previously mentioned, so the callback function is invoked when the user selects an entry from the drop-down UI component. Its output is framework_details, which is the ID of the html.Div component after the drop-down, so the framework description that's returned by this callback function is placed in that component.
# All callback functions for your UI components go here.# For example, here is the callback for UI components in the# layout_components() function that was previously described.@app.callback(Output('framework_details', 'children'),
Input('frameworks_dropdown', 'value'))defdisplay_framework_details(framework):
details = ""if framework isNone:
details = "You have not selected a framework yet"else:
if framework == "Dash":
details = "Dash is an open source framework for developing full-blown data applications using modern UI components. It is based upon Flask, Plotly.js and React.js. This tutorial describes how you can make your Dash applications 'Enterprise Ready' by using the IBM Cloud App ID service for authentication and authorization, and the IBM Cloud Code Engine service for deployment and scaling."else:
details = "Flask is a lightweight web application framework. Although it is called a 'micro framework', it is simple but extensible. It can be used to build complex dashboards like the Dash open source framework which is based upon Flask."return details
Show more
Run web server at the default port
Finally, the application runs the web server at the default 8050 port that's used by the Dash framework:
if __name__ == "__main__":
app.run_server(host = "0.0.0.0")
Show more
Step 6. Commit and push the changes to your forked repository
Commit the newly created auth_dash.py file, and the changes that you made to the Dockerfile, requirements.txt, and app.py files with the following commands:
git add auth_dash.py
git commit -a -m "Changes for the Dash application"
Show more
Push the changes to your forked repository, https://github.com/<your_github_username>/python-appid-auth with the following command:
git push
Show more
Step 7. Integrate your dashboard with App ID instance and deploy to Code Engine
Refer to the Application deployment to IBM Cloud Code Engine section of the README file and follow the steps to deploy your app. As I mentioned earlier, almost all of that section is applicable to your Dash application too. But following are two areas where you must specify a different value for a command option.
Specify your forked repository in the --source option of the ibmcloud ce build create --name python-appid-bld command, instead of the parent repository value (https://github.com/IBM/python-appid-auth.git) as follows:
ibmcloud ce build create --name python-appid-bld \
--source https://github.com/<your_github_username>/python-appid-auth.git--commit main \
--image us.icr.io/<your_container_registry_namespace>/python-appid-img \
--registry-secret python-appid-us-icr-cred \
--strategy dockerfile --size small
Use 8050 as value of the --port option in the ibmcloud ce application create --name python-appid-app command. As mentioned earlier, 8050 is the default port number used by a Dash application:
After your application successfully deploys to Code Engine, test the application in your browser either by copying and pasting the application URL or by clicking on the Open URL link in the Code Engine web UI. You are redirected to the identity provider that you configured in the App ID service and, after you log in successfully, you are redirected back to the application. The application displays the Dash UI as shown in the following screen capture.
Expand the Select drop-down list and click Flask. A description of the Flask framework displays.
Now select the Dash entry from the drop-down list. A description of the Dash framework appears.
When the access token stored in the user session expires, which will occur after one hour because its the default access token expiry, all UI components in the dashboard layout are gone. The UI displays just a "Log in" link, as shown in the following screen capture.
As previously described, this is because you set the interval property of dcc.Interval equal to the validity period of the access token. At the end of that interval, the dcc.Interval component triggers the layout_components() callback function. The @auth.check decorator on the function implicitly invokes the logic that checks whether the access token stored in the session is valid. Since it is not, the "Log in" link UI is displayed instead of the drop-down UI component.
Step 9. Develop and test your application locally
Refer to the Running the application locally on your development machine section of the README file and follow the steps. Similar to Step 7, most of this section of the code is applicable to your Dash application, but use port number 8050 in the redirect Uniform Resource Identifier (URI) as follows:
Register an additional redirect URI, http://0.0.0.0:8050/afterauth, with your App ID instance as described in the product documentation page.
Set the APPID_REDIRECT_URI environment variable to be http://0.0.0.0:8050/afterauth.
Also, install the dash dependency in addition to flask and requests with the following command:
pip3 install dash
Show more
Summary
In this tutorial, you used the IBM Cloud App ID service to add authentication and authorization to a Python web application that uses the Dash framework. Since the Dash framework is based upon the Flask web framework, this tutorial relied on the Python Flask code. Since Dash and Flask applications are very different, this tutorial described how to adapt the code in that repo to be Dash-specific with the following peculiarities.
First, instead of letting the Dash framework internally create a Flask instance, a Flask instance is explicitly supplied by using the server parameter during the following app instantiation statement: app = dash.Dash(__name__, server = auth.flask, url_base_pathname = DASH_URL_BASE_PATHNAME).
Such an explicit Flask instance is required for this implementation to define two more routes for the authentication and authorization flow in addition to the url_base_pathname route in the app instantiation statement, which is used by the Dash framework to display the dashboard UI. As a reminder, /dashboard/ is the route in this tutorial.
The first route is /startauth. It's defined in the AppIDAuthProviderDash subclass, which is used to trigger the authentication and authorization flow.
The second route is /afterauth. It's inherited from the AppIDAuthProvider superclass, which is the redirect URI that you registered with the App ID service so that the application can regain control after the authentication and authorization flow to retrieve the access token and user roles.
The Flask code performs authentication and authorization checks on each individual Flask route that needs to be protected. (Refer to the @auth.check decorator described in the Flask web application in app.py section.) This design is acceptable in a typical, multiple-route Flask application because the application keeps switching between different routes based on user actions.
On the other hand, the Dash framework renders the whole dashboard UI at a single route, which is value of the url_base_pathname property specified during the application instantiation, as shown in the app = dash.Dash(__name__, server = auth.flask, url_base_pathname = DASH_URL_BASE_PATHNAME) statement. (Again, the /dashboard/ route in this tutorial example.) Parts of the dashboard page may change depending on the callbacks associated with the UI components that a user interacts with, but the route remains the same. Then what if a user's access is revoked in App ID as described in the product documentation for removing an application user and deleting scopes and roles?
If a user's access is revoked in App ID, but the Dash application is already running in the user's browser, then the application must stop displaying the UI as soon as possible after the user's access token expires! This tutorial uses the dcc.Interval component along with the @auth.check decorator on the layout_components() callback function to fulfill this requirement.
This tutorial also described how the Dash application can be deployed to the IBM Cloud Code Engine serverless platform by closely following the procedure described in the repo readme, except for some adjustments to a couple of Code Engine commands.
Next steps
This tutorial uses a simple authorization check: does the user have at least one role defined in App ID? You can update the logic to make it stricter. For example, check whether the user has a particular, application-specific scope. Refer to the IBM Cloud API Docs for retrieving details of a specific role. Also, read about the App ID refresh token to see whether you can update the authentication logic described in the code to improve your user login experience.
About cookies on this siteOur websites require some cookies to function properly (required). In addition, other cookies may be used with your consent to analyze site usage, improve the user experience and for advertising.For more information, please review your cookie preferences options. By visiting our website, you agree to our processing of information as described in IBM’sprivacy statement. To provide a smooth navigation, your cookie preferences will be shared across the IBM web domains listed here.