# How Open Notebook's PasswordAuthMiddleware Implements Password Authentication

> Explore Open Notebook's PasswordAuthMiddleware. Learn how it validates bearer tokens against environment passwords to protect API routes.

- Repository: [Luis Novo/open-notebook](https://github.com/lfnovo/open-notebook)
- Tags: how-to-guide
- Published: 2026-06-06

---

**Open Notebook's PasswordAuthMiddleware validates Bearer tokens against an environment‑configured password, protecting all API routes except explicitly excluded paths like `/health` and `/docs`.**

The `lfnovo/open-notebook` repository uses a custom FastAPI middleware to enforce password authentication across its REST API. The `PasswordAuthMiddleware` intercepts incoming requests, verifies Bearer tokens against a secret stored in environment variables or Docker secrets, and rejects unauthorized access with standard HTTP 401 responses. This implementation ensures that sensitive notebook operations remain protected while allowing public access to health checks and documentation endpoints.

## Architecture of PasswordAuthMiddleware

The middleware is implemented as a standard FastAPI ASGI middleware class defined in [`api/auth.py`](https://github.com/lfnovo/open-notebook/blob/main/api/auth.py) (lines 12‑78). It operates by inspecting every incoming request before it reaches route handlers, performing early exit checks for excluded paths and CORS preflight requests, then validating the `Authorization` header against the configured password.

## Core Implementation in api/auth.py

### Initialization and Configuration

The middleware constructor loads the password using `get_secret_from_env("OPEN_NOTEBOOK_PASSWORD")`, which reads from environment variables or Docker secret files. It also accepts an `excluded_paths` parameter defaulting to common public routes:

```python
self.password = get_secret_from_env("OPEN_NOTEBOOK_PASSWORD")
self.excluded_paths = excluded_paths or ["/", "/health", "/docs", "/openapi.json", "/redoc"]

```

### Request Filtering Logic

The middleware applies three early exit conditions before validating credentials:

- **No password configured**: If `self.password` is empty, all requests pass through immediately, enabling local development without authentication.
- **Excluded paths**: Routes listed in `self.excluded_paths` bypass authentication entirely.
- **CORS preflight**: `OPTIONS` requests are forwarded untouched to prevent browser cross-origin blocking.

```python
if not self.password:
    return await call_next(request)

if request.url.path in self.excluded_paths:
    return await call_next(request)

if request.method == "OPTIONS":
    return await call_next(request)

```

### Bearer Token Validation

For protected routes, the middleware requires an `Authorization: Bearer <password>` header. Missing headers trigger a 401 response with `WWW-Authenticate: Bearer` challenge:

```python
auth_header = request.headers.get("Authorization")
if not auth_header:
    return JSONResponse(
        status_code=401, 
        content={"detail": "Missing authorization header"}, 
        headers={"WWW-Authenticate": "Bearer"}
    )

```

After extracting the credential, the middleware compares it against the stored password. Mismatches return identical 401 responses:

```python
if credentials != self.password:
    return JSONResponse(
        status_code=401, 
        content={"detail": "Invalid password"}, 
        headers={"WWW-Authenticate": "Bearer"}
    )

```

Upon successful validation, the request proceeds via `await call_next(request)`.

## Integration with the FastAPI Application

In [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py) (lines 73‑86), the middleware is registered before CORS handling to ensure authentication runs first:

```python
from api.auth import PasswordAuthMiddleware

app.add_middleware(
    PasswordAuthMiddleware,
    excluded_paths=[
        "/", "/health", "/docs", "/openapi.json", "/redoc",
        "/api/auth/status", "/api/config",
    ],
)

```

Notable exclusions include `/api/auth/status` (used by the frontend to detect password requirements) and `/api/config` (public configuration endpoint).

## Optional Route-Level Protection

While the middleware provides global protection, individual routes can use the `check_api_password` dependency for explicit enforcement:

```python
from fastapi import APIRouter, Depends
from api.auth import check_api_password

router = APIRouter()

@router.get("/secret")
async def secret_endpoint(valid: bool = Depends(check_api_password)):
    return {"msg": "You are authenticated!"}

```

## Configuration via Environment Variables

The password source is configured through `OPEN_NOTEBOOK_PASSWORD` or the file-based `OPEN_NOTEBOOK_PASSWORD_FILE` used by Docker secrets:

```bash

# Development .env

OPEN_NOTEBOOK_PASSWORD=my-secret-pass

# Production Docker secret

# Set OPEN_NOTEBOOK_PASSWORD_FILE=/run/secrets/notebook_pass

```

## Summary

- **Global protection**: `PasswordAuthMiddleware` in [`api/auth.py`](https://github.com/lfnovo/open-notebook/blob/main/api/auth.py) intercepts all requests and enforces Bearer token authentication.
- **Flexible configuration**: Passwords load from environment variables or Docker secrets via `get_secret_from_env()`.
- **Selective exclusions**: Routes like `/health`, `/docs`, and `/api/auth/status` bypass authentication through configurable path lists.
- **Standards-compliant**: Invalid or missing credentials produce RFC 6750-compliant 401 responses with `WWW-Authenticate: Bearer` headers.
- **Optional granularity**: The `check_api_password` dependency allows per-route authentication reinforcement when needed.

## Frequently Asked Questions

### Where is PasswordAuthMiddleware defined in the Open Notebook repository?

The middleware class is defined in [`api/auth.py`](https://github.com/lfnovo/open-notebook/blob/main/api/auth.py) (lines 12‑78). This file also contains the `check_api_password` dependency function for route-level authentication.

### How does the middleware handle development environments without passwords?

When `OPEN_NOTEBOOK_PASSWORD` is unset, the initialization logic sets `self.password` to an empty value. The `dispatch` method checks this condition first and immediately forwards requests via `await call_next(request)`, effectively disabling authentication for local development.

### Why are OPTIONS requests excluded from password validation?

The middleware explicitly skips `OPTIONS` requests to support CORS preflight checks performed by browsers before cross-origin requests. Blocking these would prevent web clients from determining allowed HTTP methods and headers.

### Can I add additional public routes that bypass authentication?

Yes. When registering the middleware in [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py), extend the `excluded_paths` list to include your custom public endpoints. The default configuration already excludes `/api/auth/status` and `/api/config` in addition to standard documentation paths.