## Dash Authorization and Login
Docs: [https://dash.plotly.com/authentication](https://dash.plotly.com/authentication)
License: MIT
Tests: [![CircleCI](https://circleci.com/gh/plotly/dash-auth.svg?style=svg)](https://circleci.com/gh/plotly/dash-auth)
For local testing, create a virtualenv, install the dev requirements, and run individual
tests or test classes:
```
python -m venv venv
. venv/bin/activate
pip install -r dev-requirements.txt
python -k ba001
```
Note that Python 3.8 or greater is required.
## Usage
### Basic Authentication
To add basic authentication, add the following to your Dash app:
```python
from dash import Dash
from dash_auth import BasicAuth
app = Dash(__name__)
USER_PWD = {
"username": "password",
"user2": "useSomethingMoreSecurePlease",
}
BasicAuth(app, USER_PWD)
```
One can also use an authorization python function instead of a dictionary/list of usernames and passwords:
```python
from dash import Dash
from dash_auth import BasicAuth
def authorization_function(username, password):
if (username == "hello") and (password == "world"):
return True
else:
return False
app = Dash(__name__)
BasicAuth(app, auth_func = authorization_function)
```
### Public routes
You can whitelist routes from authentication with the `add_public_routes` utility function,
or by passing a `public_routes` argument to the Auth constructor.
The public routes should follow [Flask's route syntax](https://flask.palletsprojects.com/en/2.3.x/quickstart/#routing).
```python
from dash import Dash
from dash_auth import BasicAuth, add_public_routes
app = Dash(__name__)
USER_PWD = {
"username": "password",
"user2": "useSomethingMoreSecurePlease",
}
BasicAuth(app, USER_PWD, public_routes=["/"])
add_public_routes(app, public_routes=["/user/<user_id>/public"])
```
NOTE: If you are using server-side callbacks on your public routes, you should also use dash_auth's new `public_callback` rather than the default Dash callback.
Below is an example of a public route and callbacks on a multi-page Dash app using Dash's pages API:
*app.py*
```python
from dash import Dash, html, dcc, page_container
from dash_auth import BasicAuth
app = Dash(__name__, use_pages=True, suppress_callback_exceptions=True)
USER_PWD = {
"username": "password",
"user2": "useSomethingMoreSecurePlease",
}
BasicAuth(app, USER_PWD, public_routes=["/", "/user/<user_id>/public"])
app.layout = html.Div(
[
html.Div(
[
dcc.Link("Home", href="/"),
dcc.Link("John Doe", href="/user/john_doe/public"),
],
style={"display": "flex", "gap": "1rem", "background": "lightgray", "padding": "0.5rem 1rem"},
),
page_container,
],
style={"display": "flex", "flexDirection": "column"},
)
if __name__ == "__main__":
app.run_server(debug=True)
```
---
*pages/home.py*
```python
from dash import Input, Output, html, register_page
from dash_auth import public_callback
register_page(__name__, "/")
layout = [
html.H1("Home Page"),
html.Button("Click me", id="home-button"),
html.Div(id="home-contents"),
]
# Note the use of public callback here rather than the default Dash callback
@public_callback(
Output("home-contents", "children"),
Input("home-button", "n_clicks"),
)
def home(n_clicks):
if not n_clicks:
return "You haven't clicked the button."
return "You clicked the button {} times".format(n_clicks)
```
---
*pages/public_user.py*
```python
from dash import html, dcc, register_page
register_page(__name__, path_template="/user/<user_id>/public")
def layout(user_id: str):
return [
html.H1(f"User {user_id} (public)"),
dcc.Link("Authenticated user content", href=f"/user/{user_id}/private"),
]
```
---
*pages/private_user.py*
```python
from dash import html, register_page
register_page(__name__, path_template="/user/<user_id>/private")
def layout(user_id: str):
return [
html.H1(f"User {user_id} (authenticated only)"),
html.Div("Members-only information"),
]
```
### OIDC Authentication
To add authentication with OpenID Connect, you will first need to set up an OpenID Connect provider (IDP).
This typically requires creating
* An application in your IDP
* Defining the redirect URI for your application, for testing locally you can use http://localhost:8050/oidc/callback
* A client ID and secret for the application
Once you have set up your IDP, you can add it to your Dash app as follows:
```python
from dash import Dash
from dash_auth import OIDCAuth
app = Dash(__name__)
auth = OIDCAuth(app, secret_key="aStaticSecretKey!")
auth.register_provider(
"idp",
token_endpoint_auth_method="client_secret_post",
# Replace the below values with your own
# NOTE: Do not hardcode your client secret!
client_id="<my-client-id>",
client_secret="<my-client-secret>",
server_metadata_url="<my-idp-.well-known-configuration>",
)
```
Once this is done, connecting to your app will automatically redirect to the IDP login page.
#### Multiple OIDC Providers
For multiple OIDC providers, you can use `register_provider` to add new ones after the OIDCAuth has been instantiated.
```python
from dash import Dash, html
from dash_auth import OIDCAuth
from flask import request, redirect, url_for
app = Dash(__name__)
app.layout = html.Div([
html.Div("Hello world!"),
html.A("Logout", href="/oidc/logout"),
])
auth = OIDCAuth(
app,
secret_key="aStaticSecretKey!",
# Set the route at which the user will select the IDP they wish to login with
idp_selection_route="/login",
)
auth.register_provider(
"IDP 1",
token_endpoint_auth_method="client_secret_post",
client_id="<my-client-id>",
client_secret="<my-client-secret>",
server_metadata_url="<my-idp-.well-known-configuration>",
)
auth.register_provider(
"IDP 2",
token_endpoint_auth_method="client_secret_post",
client_id="<my-client-id2>",
client_secret="<my-client-secret2>",
server_metadata_url="<my-idp2-.well-known-configuration>",
)
@app.server.route("/login", methods=["GET", "POST"])
def login_handler():
if request.method == "POST":
idp = request.form.get("idp")
else:
idp = request.args.get("idp")
if idp is not None:
return redirect(url_for("oidc_login", idp=idp))
return """<div>
<form>
<div>How do you wish to sign in:</div>
<select name="idp">
<option value="IDP 1">IDP 1</option>
<option value="IDP 2">IDP 2</option>
</select>
<input type="submit" value="Login">
</form>
</div>"""
if __name__ == "__main__":
app.run_server(debug=True)
```
### User-group-based permissions
`dash_auth` provides a convenient way to secure parts of your app based on user groups.
The following utilities are defined:
* `list_groups`: Returns the groups of the current user, or None if the user is not authenticated.
* `check_groups`: Checks the current user groups against the provided list of groups.
Available group checks are `one_of`, `all_of` and `none_of`.
The function returns None if the user is not authenticated.
* `protected`: A function decorator that modifies the output if the user is unauthenticated
or missing group permission.
* `protected_callback`: A callback that only runs if the user is authenticated
and with the right group permissions.
NOTE: user info is stored in the session so make sure you define a secret_key on the Flask server
to use this feature.
If you wish to use this feature with BasicAuth, you will need to define the groups for individual
basicauth users:
```python
from dash_auth import BasicAuth
app = Dash(__name__)
USER_PWD = {
"username": "password",
"user2": "useSomethingMoreSecurePlease",
}
BasicAuth(
app,
USER_PWD,
user_groups={"user1": ["group1", "group2"], "user2": ["group2"]},
secret_key="Test!",
)
# You can also use a function to get user groups
def check_user(username, password):
if username == "user1" and password == "password":
return True
if username == "user2" and password == "useSomethingMoreSecurePlease":
return True
return False
def get_user_groups(user):
if user == "user1":
return ["group1", "group2"]
elif user == "user2":
return ["group2"]
return []
BasicAuth(
app,
auth_func=check_user,
user_groups=get_user_groups,
secret_key="Test!",
)
```
Raw data
{
"_id": null,
"home_page": "https://plotly.com/dash",
"name": "dash-auth",
"maintainer": "",
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": "",
"keywords": "",
"author": "Christopher Parmer",
"author_email": "chris@plotly.com",
"download_url": "https://files.pythonhosted.org/packages/8b/8a/27c2bfb5f9fa97acedce529f859fab4ad2bae805d3a7f2b87c251ff8a6ee/dash_auth-2.3.0.tar.gz",
"platform": null,
"description": "## Dash Authorization and Login\n\nDocs: [https://dash.plotly.com/authentication](https://dash.plotly.com/authentication)\n\nLicense: MIT\n\nTests: [![CircleCI](https://circleci.com/gh/plotly/dash-auth.svg?style=svg)](https://circleci.com/gh/plotly/dash-auth)\n\nFor local testing, create a virtualenv, install the dev requirements, and run individual\ntests or test classes:\n\n```\npython -m venv venv\n. venv/bin/activate\npip install -r dev-requirements.txt\npython -k ba001\n```\n\nNote that Python 3.8 or greater is required.\n\n## Usage\n\n### Basic Authentication\n\nTo add basic authentication, add the following to your Dash app:\n\n```python\nfrom dash import Dash\nfrom dash_auth import BasicAuth\n\napp = Dash(__name__)\nUSER_PWD = {\n \"username\": \"password\",\n \"user2\": \"useSomethingMoreSecurePlease\",\n}\nBasicAuth(app, USER_PWD)\n```\n\nOne can also use an authorization python function instead of a dictionary/list of usernames and passwords:\n\n```python\nfrom dash import Dash\nfrom dash_auth import BasicAuth\n\ndef authorization_function(username, password):\n if (username == \"hello\") and (password == \"world\"):\n return True\n else:\n return False\n\n\napp = Dash(__name__)\nBasicAuth(app, auth_func = authorization_function)\n```\n\n### Public routes\n\nYou can whitelist routes from authentication with the `add_public_routes` utility function,\nor by passing a `public_routes` argument to the Auth constructor.\nThe public routes should follow [Flask's route syntax](https://flask.palletsprojects.com/en/2.3.x/quickstart/#routing).\n\n```python\nfrom dash import Dash\nfrom dash_auth import BasicAuth, add_public_routes\n\napp = Dash(__name__)\nUSER_PWD = {\n \"username\": \"password\",\n \"user2\": \"useSomethingMoreSecurePlease\",\n}\nBasicAuth(app, USER_PWD, public_routes=[\"/\"])\n\nadd_public_routes(app, public_routes=[\"/user/<user_id>/public\"])\n```\n\nNOTE: If you are using server-side callbacks on your public routes, you should also use dash_auth's new `public_callback` rather than the default Dash callback.\nBelow is an example of a public route and callbacks on a multi-page Dash app using Dash's pages API:\n\n*app.py*\n```python\nfrom dash import Dash, html, dcc, page_container\nfrom dash_auth import BasicAuth\n\napp = Dash(__name__, use_pages=True, suppress_callback_exceptions=True)\nUSER_PWD = {\n \"username\": \"password\",\n \"user2\": \"useSomethingMoreSecurePlease\",\n}\nBasicAuth(app, USER_PWD, public_routes=[\"/\", \"/user/<user_id>/public\"])\n\napp.layout = html.Div(\n [\n html.Div(\n [\n dcc.Link(\"Home\", href=\"/\"),\n dcc.Link(\"John Doe\", href=\"/user/john_doe/public\"),\n ],\n style={\"display\": \"flex\", \"gap\": \"1rem\", \"background\": \"lightgray\", \"padding\": \"0.5rem 1rem\"},\n ),\n page_container,\n ],\n style={\"display\": \"flex\", \"flexDirection\": \"column\"},\n)\n\nif __name__ == \"__main__\":\n app.run_server(debug=True)\n```\n\n---\n*pages/home.py*\n```python\nfrom dash import Input, Output, html, register_page\nfrom dash_auth import public_callback\n\nregister_page(__name__, \"/\")\n\nlayout = [\n html.H1(\"Home Page\"),\n html.Button(\"Click me\", id=\"home-button\"),\n html.Div(id=\"home-contents\"),\n]\n\n# Note the use of public callback here rather than the default Dash callback\n@public_callback(\n Output(\"home-contents\", \"children\"),\n Input(\"home-button\", \"n_clicks\"),\n)\ndef home(n_clicks):\n if not n_clicks:\n return \"You haven't clicked the button.\"\n return \"You clicked the button {} times\".format(n_clicks)\n```\n\n---\n*pages/public_user.py*\n```python\nfrom dash import html, dcc, register_page\n\nregister_page(__name__, path_template=\"/user/<user_id>/public\")\n\ndef layout(user_id: str):\n return [\n html.H1(f\"User {user_id} (public)\"),\n dcc.Link(\"Authenticated user content\", href=f\"/user/{user_id}/private\"),\n ]\n```\n\n---\n*pages/private_user.py*\n```python\nfrom dash import html, register_page\n\nregister_page(__name__, path_template=\"/user/<user_id>/private\")\n\ndef layout(user_id: str):\n return [\n html.H1(f\"User {user_id} (authenticated only)\"),\n html.Div(\"Members-only information\"),\n ]\n```\n\n### OIDC Authentication\n\nTo add authentication with OpenID Connect, you will first need to set up an OpenID Connect provider (IDP).\nThis typically requires creating\n* An application in your IDP\n* Defining the redirect URI for your application, for testing locally you can use http://localhost:8050/oidc/callback\n* A client ID and secret for the application\n\nOnce you have set up your IDP, you can add it to your Dash app as follows:\n\n```python\nfrom dash import Dash\nfrom dash_auth import OIDCAuth\n\napp = Dash(__name__)\n\nauth = OIDCAuth(app, secret_key=\"aStaticSecretKey!\")\nauth.register_provider(\n \"idp\",\n token_endpoint_auth_method=\"client_secret_post\",\n # Replace the below values with your own\n # NOTE: Do not hardcode your client secret!\n client_id=\"<my-client-id>\",\n client_secret=\"<my-client-secret>\",\n server_metadata_url=\"<my-idp-.well-known-configuration>\",\n)\n```\n\nOnce this is done, connecting to your app will automatically redirect to the IDP login page.\n\n#### Multiple OIDC Providers\n\nFor multiple OIDC providers, you can use `register_provider` to add new ones after the OIDCAuth has been instantiated.\n\n```python\nfrom dash import Dash, html\nfrom dash_auth import OIDCAuth\nfrom flask import request, redirect, url_for\n\napp = Dash(__name__)\n\napp.layout = html.Div([\n html.Div(\"Hello world!\"),\n html.A(\"Logout\", href=\"/oidc/logout\"),\n])\n\nauth = OIDCAuth(\n app,\n secret_key=\"aStaticSecretKey!\",\n # Set the route at which the user will select the IDP they wish to login with\n idp_selection_route=\"/login\",\n)\nauth.register_provider(\n \"IDP 1\",\n token_endpoint_auth_method=\"client_secret_post\",\n client_id=\"<my-client-id>\",\n client_secret=\"<my-client-secret>\",\n server_metadata_url=\"<my-idp-.well-known-configuration>\",\n)\nauth.register_provider(\n \"IDP 2\",\n token_endpoint_auth_method=\"client_secret_post\",\n client_id=\"<my-client-id2>\",\n client_secret=\"<my-client-secret2>\",\n server_metadata_url=\"<my-idp2-.well-known-configuration>\",\n)\n\n@app.server.route(\"/login\", methods=[\"GET\", \"POST\"])\ndef login_handler():\n if request.method == \"POST\":\n idp = request.form.get(\"idp\")\n else:\n idp = request.args.get(\"idp\")\n\n if idp is not None:\n return redirect(url_for(\"oidc_login\", idp=idp))\n\n return \"\"\"<div>\n <form>\n <div>How do you wish to sign in:</div>\n <select name=\"idp\">\n <option value=\"IDP 1\">IDP 1</option>\n <option value=\"IDP 2\">IDP 2</option>\n </select>\n <input type=\"submit\" value=\"Login\">\n </form>\n </div>\"\"\"\n\n\nif __name__ == \"__main__\":\n app.run_server(debug=True)\n```\n\n### User-group-based permissions\n\n`dash_auth` provides a convenient way to secure parts of your app based on user groups.\n\nThe following utilities are defined:\n* `list_groups`: Returns the groups of the current user, or None if the user is not authenticated.\n* `check_groups`: Checks the current user groups against the provided list of groups.\n Available group checks are `one_of`, `all_of` and `none_of`.\n The function returns None if the user is not authenticated.\n* `protected`: A function decorator that modifies the output if the user is unauthenticated\n or missing group permission.\n* `protected_callback`: A callback that only runs if the user is authenticated\n and with the right group permissions.\n\nNOTE: user info is stored in the session so make sure you define a secret_key on the Flask server\nto use this feature.\n\nIf you wish to use this feature with BasicAuth, you will need to define the groups for individual\nbasicauth users:\n\n```python\nfrom dash_auth import BasicAuth\n\napp = Dash(__name__)\nUSER_PWD = {\n \"username\": \"password\",\n \"user2\": \"useSomethingMoreSecurePlease\",\n}\nBasicAuth(\n app,\n USER_PWD,\n user_groups={\"user1\": [\"group1\", \"group2\"], \"user2\": [\"group2\"]},\n secret_key=\"Test!\",\n)\n\n# You can also use a function to get user groups\ndef check_user(username, password):\n if username == \"user1\" and password == \"password\":\n return True\n if username == \"user2\" and password == \"useSomethingMoreSecurePlease\":\n return True\n return False\n\ndef get_user_groups(user):\n if user == \"user1\":\n return [\"group1\", \"group2\"]\n elif user == \"user2\":\n return [\"group2\"]\n return []\n\nBasicAuth(\n app,\n auth_func=check_user,\n user_groups=get_user_groups,\n secret_key=\"Test!\",\n)\n```\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Dash Authorization Package.",
"version": "2.3.0",
"project_urls": {
"Homepage": "https://plotly.com/dash"
},
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "1942d636affcd8074f7f48156a99d8854e5be951d8601dcb4178231c92c988d1",
"md5": "0f246d08c51fe10999df7dd79953b396",
"sha256": "2fa72d4ee128b4f9cf0c958157a986ffcd0b93d43b54d64e3cef5976ab7e0abe"
},
"downloads": -1,
"filename": "dash_auth-2.3.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "0f246d08c51fe10999df7dd79953b396",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 14428,
"upload_time": "2024-03-19T01:37:37",
"upload_time_iso_8601": "2024-03-19T01:37:37.878906Z",
"url": "https://files.pythonhosted.org/packages/19/42/d636affcd8074f7f48156a99d8854e5be951d8601dcb4178231c92c988d1/dash_auth-2.3.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "8b8a27c2bfb5f9fa97acedce529f859fab4ad2bae805d3a7f2b87c251ff8a6ee",
"md5": "a549c2880434b9539e7d42c0823d5cda",
"sha256": "72df43248c15e121f8d5d710981e880deb2df546b564f2951a10ca50d7d92f6d"
},
"downloads": -1,
"filename": "dash_auth-2.3.0.tar.gz",
"has_sig": false,
"md5_digest": "a549c2880434b9539e7d42c0823d5cda",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 17727,
"upload_time": "2024-03-19T01:37:41",
"upload_time_iso_8601": "2024-03-19T01:37:41.275627Z",
"url": "https://files.pythonhosted.org/packages/8b/8a/27c2bfb5f9fa97acedce529f859fab4ad2bae805d3a7f2b87c251ff8a6ee/dash_auth-2.3.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-03-19 01:37:41",
"github": false,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"lcname": "dash-auth"
}