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
Custom backends 1
Protected / public callbacks
Async callbacks 2
Authenticated WebSocket callbacks 3

✅ supported · ❌ not supported

1 detect_backend resolves Flask/Quart/FastAPI automatically; any other server is supported by supplying your own Backend instance.

2 Provided by the Quart and FastAPI backends.

3 Provided by the Quart and FastAPI backends. 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)

Async backends

Known Limitations

⚠️ WebSocket callbacks & auth: Do not enable websocket_callbacks=True globally on an authenticated use_pages app. The global flag routes every callback — including Dash's built-in page-routing callback — over the WebSocket, which bypasses the HTTP before_request auth guard where the login challenge is issued. Navigating to a protected page then hangs (the socket closes with 4401 and reconnect-loops) instead of prompting for login — the prompt appears only after a full page reload. Opt individual streaming callbacks into websocket=True instead, so routing and login stay on HTTP.

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.

FastAPI (async) Backend

dash-auth-async supports Dash's FastAPI backend too. Install the fastapi extra to pull in the required dependencies:

pip install dash-auth-async[fastapi]

Then pass backend="fastapi" when creating your Dash app. BasicAuth and OIDCAuth work exactly as on Flask/Quart — no code changes beyond the backend flag.

BasicAuth with FastAPI
from dash import Dash
from dash_auth_async import BasicAuth

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

BasicAuth(
    app,
    {"admin": "admin", "viewer": "viewer123"},
    secret_key="aStaticSecretKey!",  # enables sessions (SessionMiddleware)
)

if __name__ == "__main__":
    app.run(host="127.0.0.1", port=8050, debug=True)
OIDCAuth with FastAPI
import os
from dash import Dash, html
from dash_auth_async import OIDCAuth

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

app.layout = html.Div([
    html.H2("OIDCAuth + FastAPI"),
    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)

Notes:

  • A secret_key installs Starlette's SessionMiddleware automatically. If you add your own SessionMiddleware, dash-auth-async defers to it.
  • Auth/OIDCAuth must be constructed before the server starts serving (Starlette forbids adding middleware after startup) — the normal usage pattern.
  • OIDC uses authlib's official starlette_client; no extra client module required.

The FastAPI 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.3.1.tar.gz (40.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.3.1-py3-none-any.whl (39.3 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: dash_auth_async-1.3.1.tar.gz
  • Upload date:
  • Size: 40.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.3.1.tar.gz
Algorithm Hash digest
SHA256 b94dd59d979a7681cde076f836d6753b4fc3cae686071697dc64f38f7690bf32
MD5 88060fb111f211ddd64500c5d3984ab9
BLAKE2b-256 18e481fc7851c7c3a1575d7d1689ea513b7d243625e61be140da6f060b0f5c20

See more details on using hashes here.

Provenance

The following attestation bundles were made for dash_auth_async-1.3.1.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.3.1-py3-none-any.whl.

File metadata

  • Download URL: dash_auth_async-1.3.1-py3-none-any.whl
  • Upload date:
  • Size: 39.3 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.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 2a794863a69056eebb0a7bfb31d69ac2e29ad5630880f869c6ccba7811e77cc8
MD5 c13fe1d43a7e24e2cc80adff87dcd284
BLAKE2b-256 dee4fa2bb8c4c5c8572c410b5a2d0726226f7bf1fbd105b28258a8cedee89597

See more details on using hashes here.

Provenance

The following attestation bundles were made for dash_auth_async-1.3.1-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