How to Replace Open Notebook's Authentication Middleware for Production

Open Notebook currently uses a simple PasswordAuthMiddleware that validates a static password against the X-Password header, but you should replace it with OAuth2/JWT-based authentication before deploying to production.

Open Notebook (lfnovo/open-notebook) is a research assistant API that currently ships with a development-only authentication scheme. While the PasswordAuthMiddleware defined in api/auth.py provides basic protection via a single shared secret, it lacks user granularity, token revocation, and standard security protocols required for production workloads.

Current Authentication Implementation

The PasswordAuthMiddleware Component

In api/auth.py, the PasswordAuthMiddleware class extends FastAPI's BaseHTTPMiddleware. It reads the OPEN_NOTEBOOK_PASSWORD environment variable (falling back to a default if unset) and expects clients to transmit this value in the X-Password request header. The middleware is registered globally in api/main.py via app.add_middleware(PasswordAuthMiddleware, ...).

Whitelisted Routes and Bypass Logic

The middleware automatically skips authentication for a hard-coded list of public paths including /, /health, /docs, /openapi.json, /redoc, /api/auth/status, and /api/config. This allows OpenAPI documentation and health checks to remain accessible without credentials.

Limitations for Production Use

This approach is unsuitable for production because it relies on a single static secret that cannot be revoked per user, offers no role-based access control, and lacks integration with modern identity providers like Auth0, Azure AD, or Keycloak. If the password is compromised, the entire API is exposed without audit trails or granular permission systems.

Production Replacement Strategy

Step 1 – Remove the Global Middleware

Delete the app.add_middleware(PasswordAuthMiddleware, ...) block from api/main.py (approximately lines 73–86). Keep the CORS middleware configuration, but remove the password-based guard to prevent conflicts with the new security layer.

Step 2 – Implement JWT Security Layer

Create a new api/security.py file to handle token creation and validation. Use fastapi.security.HTTPBearer to extract Bearer tokens from the Authorization header and PyJWT to verify signatures against a JWT_SECRET_KEY environment variable.

Step 3 – Protect Routes with Dependencies

Replace global middleware with per-router dependencies. Import get_current_user from your new security module and apply it to protected routers using dependencies=[Depends(get_current_user)]. This allows the /api/auth/login endpoint in api/routers/auth.py to remain public while securing research assistant endpoints.

Step 4 – Update Environment Configuration

Add JWT_SECRET_KEY and ACCESS_TOKEN_EXPIRE_MINUTES to your CONFIGURATION.md and .env files. Store the JWT signing key in a secrets manager rather than version control, and configure rotation policies to maintain security hygiene.

Implementation Code Examples

New Security Module

Create api/security.py with the following content:


# api/security.py

import os
from datetime import datetime, timedelta
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
import jwt

bearer = HTTPBearer(auto_error=False)
SECRET_KEY = os.getenv("JWT_SECRET_KEY", "change-me-in-production")
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = int(os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", "60"))

def create_access_token(data: dict) -> str:
    to_encode = data.copy()
    expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    to_encode.update({"exp": expire})
    return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)

async def get_current_user(
    credentials: HTTPAuthorizationCredentials = Depends(bearer),
):
    if credentials is None:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Not authenticated",
            headers={"WWW-Authenticate": "Bearer"},
        )
    try:
        payload = jwt.decode(credentials.credentials, SECRET_KEY, algorithms=[ALGORITHM])
        return payload
    except jwt.PyJWTError:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid token",
            headers={"WWW-Authenticate": "Bearer"},
        )

Updating the Main Application

Modify api/main.py to remove the old middleware and apply the new dependency:


# api/main.py (excerpt)

from fastapi import FastAPI, Depends, APIRouter
from contextlib import asynccontextmanager
from api.security import get_current_user
from api.routers import auth, notes

@asynccontextmanager
async def lifespan(app: FastAPI):
    # startup/shutdown logic

    yield

app = FastAPI(
    title="Open Notebook API",
    lifespan=lifespan,
)

# Remove this block entirely:

# from api.auth import PasswordAuthMiddleware

# app.add_middleware(PasswordAuthMiddleware, ...)

# Apply authentication to specific routers

protected_router = APIRouter(
    prefix="/api/notes",
    dependencies=[Depends(get_current_user)],
)
app.include_router(protected_router)
app.include_router(auth.router)

@app.get("/health")
async def health_check():
    return {"status": "ok"}

Creating the Login Endpoint

Update api/routers/auth.py to issue JWT tokens instead of validating the static password for session management:


# api/routers/auth.py

from fastapi import APIRouter, HTTPException, status
from api.security import create_access_token
import os

router = APIRouter()

@router.post("/login")
async def login(password: str):
    # Validate against your user database or external IdP

    if password != os.getenv("OPEN_NOTEBOOK_PASSWORD"):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect password",
        )
    token = create_access_token({"sub": "user-id", "role": "researcher"})
    return {"access_token": token, "token_type": "bearer"}

Configuration Changes

Update CONFIGURATION.md to document the new variables:

  • JWT_SECRET_KEY: RSA private key or HMAC secret for signing tokens. Store in a secrets manager.
  • ACCESS_TOKEN_EXPIRE_MINUTES: Token lifetime (default: 60).
  • OPEN_NOTEBOOK_PASSWORD: Deprecated for API access; use only for initial admin bootstrap if needed.

Summary

  • Open Notebook's default PasswordAuthMiddleware in api/auth.py uses a single static password from OPEN_NOTEBOOK_PASSWORD and the X-Password header, which is insecure for production.
  • Remove the middleware registration from api/main.py and implement a JWT-based scheme in a new api/security.py module.
  • Protect routes using FastAPI's Depends(get_current_user) rather than global middleware to enable granular access control and proper OpenAPI documentation.
  • Store JWT_SECRET_KEY in environment variables or secrets managers and document the changes in CONFIGURATION.md.

Frequently Asked Questions

What authentication middleware does Open Notebook use by default?

The default middleware is PasswordAuthMiddleware, defined in api/auth.py. It validates a static password provided via the X-Password header against the OPEN_NOTEBOOK_PASSWORD environment variable and skips authentication for whitelisted paths like /docs and /health.

Why is the password-based middleware not suitable for production?

It relies on a single shared secret that cannot be revoked per user, lacks role-based access control, and does not support standard protocols like OAuth2 or OpenID Connect. If the password is leaked, the entire API is compromised without audit trails.

Can I replace the middleware with API keys instead of JWT?

Yes. You can modify api/security.py to validate API keys against a database or secure store instead of decoding JWTs. Use fastapi.security.APIKeyHeader to extract the key from headers and replace the get_current_user dependency with a key-validation function.

How do I migrate existing clients to the new authentication method?

Update client applications to request a token from /api/auth/login (using the existing password temporarily) and then include that token in subsequent requests via the Authorization: Bearer <token> header. Gradually deprecate the X-Password header once all clients have migrated.

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 →