Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
7b62927
[feat]: Scaffold Quart websocket auth example shell.
joschrag Jun 15, 2026
663a00b
[feat]: Add public live counter/clock websocket page.
joschrag Jun 15, 2026
d3a632f
[feat]: Add private progress-task websocket page.
joschrag Jun 15, 2026
5a52fb2
[refactor]: Drop redundant final set_props in private task.
joschrag Jun 15, 2026
70f02f5
[docs]: Add README for Quart websocket auth example.
joschrag Jun 15, 2026
e985613
[chore]: Add FastAPI test stack to dev dependencies.
joschrag Jun 17, 2026
9e54db8
[feat]: Add divergent-operation methods to Backend ABC with Flask/Qua…
joschrag Jun 17, 2026
5a4925f
[feat]: Add FastAPIBackend core (ContextVar, request/session, url_for…
joschrag Jun 17, 2026
e9ea8b6
[feat]: Implement FastAPIBackend.register_auth_hook (pure-ASGI middle…
joschrag Jun 17, 2026
bcb3343
[feat]: Add FastAPIBackend.setup_session and session_configured.
joschrag Jun 17, 2026
10e0ce5
[refactor]: Route BasicAuth secret_key through backend.setup_session.
joschrag Jun 17, 2026
35ea782
[feat]: Guard _current_user against unavailable session (FastAPI no-s…
joschrag Jun 17, 2026
f155164
[feat]: Route public-route/callback storage through the backend (Fast…
joschrag Jun 17, 2026
a2b14fc
[test]: BasicAuth FastAPI integration spike + graceful threaded shutd…
joschrag Jun 17, 2026
552d10d
[test]: Complete BasicAuth FastAPI integration matrix (groups + auth_…
joschrag Jun 17, 2026
6e6178c
[feat]: Add FastAPIBackend.add_route, make_oauth, and current_path.
joschrag Jun 17, 2026
f68c109
[feat]: Wire OIDCAuth through the backend; add FastAPI views and get_…
joschrag Jun 17, 2026
ae46bee
[fix]: Annotate FastAPI OIDC view request params so Starlette injects…
joschrag Jun 17, 2026
631d665
[test]: Add OIDCAuth FastAPI integration tests with mocked IDP.
joschrag Jun 17, 2026
3ea3a3d
[docs]: Add FastAPI (async) backend section to README and mark it sup…
joschrag Jun 17, 2026
839e581
[chore]: Combine quart import in test after enabling combine-as-imports.
joschrag Jun 17, 2026
3109183
[fix]: Wire secure_session through setup_session and harden FastAPI s…
joschrag Jun 18, 2026
5175b57
[test]: Rename auth_func login tests off the ba002/ba003 ids to ba004…
joschrag Jun 18, 2026
8636ab2
[chore]: Ignore local scratch dirs and scope docstring rules off exam…
joschrag Jun 18, 2026
cfa67e1
[refactor]: Resolve the backend via the cached active backend in publ…
joschrag Jun 18, 2026
a3f6ca9
[refactor]: Replace OIDC isinstance dispatch with backend polymorphis…
joschrag Jun 18, 2026
4b8c677
[fix]: Emit http.disconnect once the replayed request body is consumed.
joschrag Jun 18, 2026
1cc192a
[test]: Cover the FastAPI fail-closed branches and real authlib OIDC …
joschrag Jun 18, 2026
77be385
[test]: Add failing FastAPI WebSocket security tests (public/group pa…
joschrag Jun 18, 2026
24bd6f5
[test]: Explain the FastAPI suppress_origin quirk in the WS security …
joschrag Jun 18, 2026
aff8c27
[fix]: Authenticate FastAPI WebSocket callbacks via Backend.ws_identity.
joschrag Jun 18, 2026
f967a8b
[refactor]: Migrate WS callback_map lazily in the message hook, not a…
joschrag Jun 18, 2026
1b00e1f
[test]: Cover the FastAPI protected WebSocket streaming auth path end…
joschrag Jun 18, 2026
7bf9415
[test]: Fix WS hook unit test for backend-driven identity and lazy se…
joschrag Jun 18, 2026
4a2adbd
[fix]: Resolve the public-route helpers' backend from app.server, not…
joschrag Jun 18, 2026
fa38ff8
[chore]: Fix typo in filenames.
joschrag Jun 18, 2026
e4fec15
[chore]: Add limitation section for quart and fastapi backends.
joschrag Jun 18, 2026
115a37f
[feat]: Reconnect WebSocket auth on login by retiring the browser's p…
joschrag Jun 19, 2026
3116766
[chore]: Release v1.3.0.
joschrag Jun 19, 2026
05ad386
[test]: Cover the unauthenticated branch of async protected callbacks…
joschrag Jun 19, 2026
5dfb8b0
[chore]: Update feature matrix.
joschrag Jun 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ vv
.tox/
venv/

# Local scratch / repro scripts — not part of the package, kept out of the
# tracked tree so `ruff check .` (which respects .gitignore) stays green.
scripts/
.coverage
.vscode/

node_modules
npm-debug.log
.DS_Store
Expand Down
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [1.3.0] - 2026-06-19

### Added
- FastAPI (async) backend support for `BasicAuth` and `OIDCAuth` — run on Starlette/ASGI with `Dash(__name__, backend="fastapi")`. Auth is enforced by pure-ASGI middleware, sessions use Starlette `SessionMiddleware`, and public routes/callbacks are stored on the app's `server.state`
- Authenticated WebSocket callbacks on the FastAPI backend: each `callback_request` is authorized via `Backend.ws_identity` and fails closed, while public callbacks still stream unauthenticated
- WebSocket authentication now reconnects on login. When a browser authenticates over HTTP, its stale pre-login WebSocket is retired so the renderer reconnects with the authenticated session — eliminating the "first click is dropped / only works on the second click" behaviour for callbacks invoked over a socket opened before login. Applies to both the FastAPI and Quart backends

### Changed
- The WebSocket `callback_map` is migrated lazily on the first `callback_request` rather than at `Auth(...)` construction, so a global `@callback` registered after `Auth(...)` is still picked up and a WebSocket-first client no longer hits an empty map

### Fixed
- The public-route helpers resolve the backend from `app.server` instead of a process-global fallback, keeping routing correct when several apps share a process
- `secure_session` is honoured through `setup_session`, and the FastAPI session lookup is hardened so it raises a clear error (rather than `KeyError`) under `python -O`
- FastAPI OIDC views annotate their request parameter so Starlette injects the request, and the ASGI body-replay emits `http.disconnect` once the cached body is consumed

## [1.2.1] - 2026-06-17

### Fixed
Expand Down
106 changes: 96 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,20 @@ How this fork compares to upstream [`dash-auth`](https://github.com/plotly/dash-
| --- | :---: | :---: |
| Flask backend | ✅ | ✅ |
| Quart backend | ❌ | ✅ |
| FastAPI backend | ❌ | 🚧 <sup>1</sup> |
| Custom backends | ❌ | ✅ <sup>2</sup> |
| FastAPI backend | ❌ | |
| Custom backends | ❌ | ✅ <sup>1</sup> |
| Protected / public callbacks | ✅ | ✅ |
| Async callbacks | ❌ | ✅ <sup>3</sup> |
| Async callbacks | ❌ | ✅ <sup>2</sup> |
| Authenticated WebSocket callbacks | ❌ | ✅ <sup>3</sup> |

✅ supported · 🚧 on the roadmap · ❌ not supported
✅ supported · ❌ not supported

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

<sup>2</sup> Provided by the Quart and FastAPI backends.

<sup>3</sup> Provided by the Quart and FastAPI backends. WebSocket auth is a no-op on Flask, which has no WebSocket callback transport.

<sup>1</sup> 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())`.
<sup>2</sup> `detect_backend` resolves Flask/Quart automatically; any other server is supported by supplying your own `Backend` instance.
<sup>3</sup> Provided by the Quart backend. WebSocket auth is a no-op on Flask, which has no WebSocket callback transport.

For local testing, install [uv](https://docs.astral.sh/uv/getting-started/installation/), then install the dev dependencies and run individual tests:

Expand Down Expand Up @@ -276,7 +279,22 @@ if __name__ == "__main__":
app.run(debug=True)
```

### Quart (async) Backend
### 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](https://dash.plotly.com/) for fully async request handling.
Install the `quart` extra to pull in the required dependencies:
Expand All @@ -288,7 +306,7 @@ 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
##### BasicAuth with Quart

```python
from dash import Dash
Expand All @@ -302,7 +320,7 @@ if __name__ == "__main__":
app.run(host="127.0.0.1", port=8050, debug=True)
```

#### OIDCAuth with Quart
##### OIDCAuth with Quart

```python
import os
Expand Down Expand Up @@ -332,6 +350,74 @@ if __name__ == "__main__":

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

#### FastAPI (async) Backend

`dash-auth-async` supports [Dash's FastAPI backend](https://dash.plotly.com/) 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

```python
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

```python
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.
Expand Down
Loading
Loading