# Open Notebook REST API Authentication Flow with PasswordAuthMiddleware

> Explore the Open Notebook REST API authentication flow. Learn how PasswordAuthMiddleware validates Bearer tokens against OPEN_NOTEBOOK_PASSWORD for secure endpoint access. Get 401 Unauthorized for invalid credentials.

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

---

**Open Notebook secures every API endpoint through a global PasswordAuthMiddleware that validates Bearer tokens against the `OPEN_NOTEBOOK_PASSWORD` environment variable, returning 401 Unauthorized for invalid credentials while exempting health checks and documentation paths.**

The Open Notebook project implements a simple yet effective password-based authentication layer for its FastAPI REST API. Every incoming request passes through the `PasswordAuthMiddleware` registered in [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py), which intercepts traffic before it reaches route handlers. This design provides stateless, single-password protection suitable for self-hosted deployments without requiring complex user management systems.

## How PasswordAuthMiddleware Works

The authentication system centers on a custom ASGI middleware class that wraps the entire FastAPI application. This middleware inspects every incoming request for valid credentials before allowing access to protected resources.

### Middleware Registration in api/main.py

In [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py), the middleware attaches to the app instance with a specific list of public paths that bypass authentication:

```python

# api/main.py

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

```

The `excluded_paths` parameter ensures that health checks, API documentation, and configuration endpoints remain publicly accessible. The middleware processes these exemptions before performing any credential validation.

### Password Source Configuration

The middleware retrieves the authentication password from environment variables or Docker secrets via the `get_secret_from_env` utility function. In [`api/auth.py`](https://github.com/lfnovo/open-notebook/blob/main/api/auth.py), the initialization logic calls:

```python

# api/auth.py

self.password = get_secret_from_env("OPEN_NOTEBOOK_PASSWORD")

```

This allows secure password injection through environment variables or container orchestration secret management systems.

## Authentication Flow and Validation Logic

The `PasswordAuthMiddleware` implements a strict validation pipeline with multiple exit points that determine whether a request proceeds or receives an immediate 401 response.

### Early Exit Conditions

The middleware short-circuits authentication checks under three specific conditions:

- **No password configured**: If `OPEN_NOTEBOOK_PASSWORD` is empty or unset, the middleware bypasses all authentication, allowing unrestricted access. This configuration is intended strictly for local development environments.
- **Excluded paths**: Requests targeting URLs listed in `excluded_paths` (such as `/health` or `/docs`) proceed without credential checks.
- **CORS preflight**: All `OPTIONS` requests receive automatic pass-through to handle browser cross-origin preflight checks.

### Header Validation

For protected routes, the middleware requires an `Authorization` header formatted exactly as `Bearer <password>`. The validation logic splits the header into scheme and credentials components:

```python

# api/auth.py (excerpt)

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

scheme, credentials = auth_header.split(" ", 1)
if scheme.lower() != "bearer":
    return JSONResponse(
        status_code=401,
        content={"detail": "Invalid authentication scheme"},
        headers={"WWW-Authenticate": "Bearer"}
    )

```

Malformed headers or incorrect schemes trigger an immediate 401 response with the `WWW-Authenticate: Bearer` challenge header.

### Password Verification

After parsing the header, the middleware compares the provided credentials against the configured password:

```python

# api/auth.py (excerpt)

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

```

When credentials match, the middleware calls `await self.app(scope, receive, send)` to pass the request to the next layer. Mismatched passwords return 401 without exposing whether the password was missing or incorrect.

## Per-Route Authentication with check_api_password

For finer-grained control or explicit dependency injection, individual routes can utilize the `check_api_password` function. Located in [`api/auth.py`](https://github.com/lfnovo/open-notebook/blob/main/api/auth.py), this dependency performs the same validation logic as the middleware but integrates with FastAPI's `Depends` system:

```python

# api/auth.py

async def check_api_password(security: HTTPBasicCredentials = Depends(security)):
    # Validation logic identical to middleware

    ...

```

Routes using this dependency receive an additional authentication layer even if the global middleware were bypassed or modified.

## Implementation Examples

### Client Request with Bearer Token

To authenticate API requests from Python clients, include the password in the Authorization header using the Bearer scheme:

```python
import requests

API_URL = "http://localhost:5055/api/notebooks"
PASSWORD = "my-secret-pwd"

headers = {"Authorization": f"Bearer {PASSWORD}"}
response = requests.get(API_URL, headers=headers)

print(response.status_code)  # 200 if authentication succeeds

print(response.json())

```

### Using check_api_password in Custom Routes

When creating custom endpoints that explicitly require authentication confirmation:

```python

# api/routers/custom.py

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

router = APIRouter()

@router.get("/secret")
def secret_endpoint(allowed: bool = Depends(check_api_password)):
    return {"msg": "You have access to the secret data!"}

```

Register this router in [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py) to automatically inherit the global middleware protection while gaining explicit dependency validation.

### Disabling Authentication for Development

To run the API without authentication during development, set the environment variable to an empty value:

```bash

# .env

OPEN_NOTEBOOK_PASSWORD=

```

With no password configured, the middleware immediately passes all requests to route handlers without header validation.

## Summary

- **Global protection**: The `PasswordAuthMiddleware` in [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py) intercepts all requests before they reach FastAPI route handlers.
- **Environment-based credentials**: Authentication relies on the `OPEN_NOTEBOOK_PASSWORD` variable retrieved via `get_secret_from_env()` in [`api/auth.py`](https://github.com/lfnovo/open-notebook/blob/main/api/auth.py).
- **Bearer token format**: Valid requests must include `Authorization: Bearer <password>` headers; malformed or missing headers return 401 with `WWW-Authenticate: Bearer`.
- **Path exemptions**: Health checks (`/health`), documentation (`/docs`, `/redoc`), and configuration endpoints (`/api/config`, `/api/auth/status`) remain publicly accessible.
- **Development mode**: Empty password configuration disables authentication entirely, allowing unrestricted local access.
- **Optional dependency**: The `check_api_password` function provides per-route authentication validation for additional security layers.

## Frequently Asked Questions

### What happens if OPEN_NOTEBOOK_PASSWORD is not set?

When the `OPEN_NOTEBOOK_PASSWORD` environment variable is empty or undefined, the `PasswordAuthMiddleware` bypasses all authentication checks. This configuration allows unrestricted access to all endpoints and is intended strictly for local development environments. Production deployments should always configure a strong password.

### Which API paths are excluded from authentication?

The middleware configuration in [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py) explicitly excludes the following paths: `/`, `/health`, `/docs`, [`/openapi.json`](https://github.com/lfnovo/open-notebook/blob/main//openapi.json), `/redoc`, `/api/auth/status`, and `/api/config`. Additionally, all HTTP `OPTIONS` requests (CORS preflight) bypass authentication to support browser-based cross-origin requests.

### How do I authenticate requests from a client application?

Send an HTTP header named `Authorization` with the value formatted as `Bearer your-password-here`. For example: `Authorization: Bearer my-secure-password`. Requests without this header, or with incorrect passwords, receive a 401 Unauthorized response with a `WWW-Authenticate: Bearer` challenge header.

### Can I use PasswordAuthMiddleware alongside other authentication methods?

The current implementation in [`api/auth.py`](https://github.com/lfnovo/open-notebook/blob/main/api/auth.py) uses a single global password. While the middleware architecture supports extension, the base `PasswordAuthMiddleware` class validates only the Bearer token against `OPEN_NOTEBOOK_PASSWORD`. For multi-user or token-based authentication (JWT, API keys), you would need to extend the middleware or implement additional FastAPI dependencies beyond the current `check_api_password` function.