Open Notebook REST API Authentication Flow with PasswordAuthMiddleware

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, 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, the middleware attaches to the app instance with a specific list of public paths that bypass authentication:


# 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, the initialization logic calls:


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


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


# 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, this dependency performs the same validation logic as the middleware but integrates with FastAPI's Depends system:


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

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:


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


# .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 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.
  • 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 explicitly excludes the following paths: /, /health, /docs, /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 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →