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_PASSWORDis 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/healthor/docs) proceed without credential checks. - CORS preflight: All
OPTIONSrequests 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
PasswordAuthMiddlewareinapi/main.pyintercepts all requests before they reach FastAPI route handlers. - Environment-based credentials: Authentication relies on the
OPEN_NOTEBOOK_PASSWORDvariable retrieved viaget_secret_from_env()inapi/auth.py. - Bearer token format: Valid requests must include
Authorization: Bearer <password>headers; malformed or missing headers return 401 withWWW-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_passwordfunction 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →