Skip to main content

Dash Authorization Package.

Project description

Dash Authorization and Login

Lint Typecheck codecov Ruff ty PyPI Python License

Maintained by joschrag. Forked from plotly/dash-auth with the goal to add support for the new 4.x dash backends.

License: MIT

Feature overview

How this fork compares to upstream dash-auth. The headline difference is async: upstream is Flask-only, while dash-auth-async adds the Quart backend and everything that rides on it (async callbacks, authenticated WebSocket callbacks) plus a pluggable backend abstraction.

Capability dash-auth (upstream) dash-auth-async
Flask backend
Quart backend
FastAPI backend 🚧 1
Custom backends 2
Protected / public callbacks
Async callbacks 3
Authenticated WebSocket callbacks 3

✅ supported · 🚧 on the roadmap · ❌ not supported

1 A dash-auth-async[fastapi] extra is declared and a native FastAPI backend is on the roadmap. In the meantime you can support it by implementing the Backend ABC and passing Auth(..., backend=MyBackend()). 2 detect_backend resolves Flask/Quart automatically; any other server is supported by supplying your own Backend instance. 3 Provided by the Quart backend. WebSocket auth is a no-op on Flask, which has no WebSocket callback transport.

For local testing, install uv, then install the dev dependencies and run individual tests:

uv sync
uv run pytest -k ba001

Note that Python 3.10 or greater is required.

Usage

Basic Authentication

To add basic authentication, add the following to your Dash app:

from dash import Dash
from dash_auth_async 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:

from dash import Dash
from dash_auth_async 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.

from dash import Dash
from dash_auth_async 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_async'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

from dash import Dash, html, dcc, page_container
from dash_auth_async 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(debug=True)

pages/home.py

from dash import Input, Output, html, register_page
from dash_auth_async 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

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

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:

from dash import Dash
from dash_auth_async 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.

from dash import Dash, html
from dash_auth_async 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(debug=True)

Quart (async) Backend

dash-auth-async supports Dash's Quart backend for fully async request handling. Install the quart extra to pull in the required dependencies:

pip install dash-auth-async[quart]

Then pass backend="quart" when creating your Dash app. The auth setup is identical to the Flask examples above — no code changes required beyond the backend flag.

BasicAuth with Quart

from dash import Dash
from dash_auth_async import BasicAuth

app = Dash(__name__, backend="quart")

BasicAuth(app, {"admin": "admin", "viewer": "viewer123"})

if __name__ == "__main__":
    app.run(host="127.0.0.1", port=8050, debug=True)

OIDCAuth with Quart

import os
from dash import Dash, html
from dash_auth_async import OIDCAuth

app = Dash(__name__, backend="quart")

app.layout = html.Div([
    html.H2("OIDCAuth + Quart"),
    html.A("Logout", href="/oidc/logout"),
])

auth = OIDCAuth(app, secret_key="aStaticSecretKey!")
auth.register_provider(
    "myidp",
    client_id=os.environ["OIDC_CLIENT_ID"],
    client_secret=os.environ["OIDC_CLIENT_SECRET"],
    server_metadata_url=os.environ["OIDC_METADATA_URL"],
    token_endpoint_auth_method="client_secret_post",
    client_kwargs={"scope": "openid email profile"},
)

if __name__ == "__main__":
    app.run(host="127.0.0.1", port=8050, debug=True)

Note: The Quart backend requires Dash >= 4.2.0 and Python >= 3.10.

User-group-based permissions

dash_auth_async 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:

from dash_auth_async 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!",
)

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dash_auth_async-1.2.0.tar.gz (27.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dash_auth_async-1.2.0-py3-none-any.whl (26.7 kB view details)

Uploaded Python 3

File details

Details for the file dash_auth_async-1.2.0.tar.gz.

File metadata

  • Download URL: dash_auth_async-1.2.0.tar.gz
  • Upload date:
  • Size: 27.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for dash_auth_async-1.2.0.tar.gz
Algorithm Hash digest
SHA256 911425d54e90135dbb0944aaf085b6452c9e16989a2348a76ad063c954fea1e5
MD5 2db0a08b45804ecf58eba16c90252c55
BLAKE2b-256 627c1c45b8c595de314aae5102e8a648e4a460ee484a3e6cc29151ed6877a005

See more details on using hashes here.

Provenance

The following attestation bundles were made for dash_auth_async-1.2.0.tar.gz:

Publisher: release.yaml on joschrag/dash-auth-async

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file dash_auth_async-1.2.0-py3-none-any.whl.

File metadata

  • Download URL: dash_auth_async-1.2.0-py3-none-any.whl
  • Upload date:
  • Size: 26.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for dash_auth_async-1.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 591ef591c130c8f3f7a7762c4b22710fae0ca2a6ac8fe09a9670aa818dd8cabe
MD5 b7afc1870be373039cc5b1824660550a
BLAKE2b-256 d9a3f97b574c51503ff317241609a47af70c6244493166e848dd47073e048960

See more details on using hashes here.

Provenance

The following attestation bundles were made for dash_auth_async-1.2.0-py3-none-any.whl:

Publisher: release.yaml on joschrag/dash-auth-async

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page